001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html.image.resource;
018
019import java.awt.Graphics2D;
020import java.awt.image.BufferedImage;
021import java.lang.ref.SoftReference;
022import java.time.Instant;
023import org.apache.wicket.request.resource.DynamicImageResource;
024
025
026/**
027 * A DynamicImageResource subclass that allows easy rendering of regeneratable (unbuffered) dynamic
028 * images. A RenderedDynamicImageResource implements the abstract method render(Graphics2D) to
029 * create/re-create a given image on-the-fly. When a RenderedDynamicImageResource is serialized, the
030 * image state is transient, which means it will disappear when the resource is sent over the wire
031 * and then will be recreated when required.
032 * <p>
033 * The format of the image (and therefore the resource's extension) can be specified with
034 * setFormat(String). The default format is "PNG" because JPEG is lossy and makes generated images
035 * look bad and GIF has patent issues.
036 * 
037 * @see org.apache.wicket.markup.html.image.resource.DefaultButtonImageResource
038 * @see org.apache.wicket.markup.html.image.resource.DefaultButtonImageResourceFactory
039 * @author Jonathan Locke
040 * @author Gili Tzabari
041 * @author Johan Compagner
042 */
043public abstract class RenderedDynamicImageResource extends DynamicImageResource
044{
045        private static final long serialVersionUID = 1L;
046
047        /** Height of image */
048        private int height = 100;
049
050        /** Transient image data so that image only needs to be generated once per VM */
051        private transient SoftReference<byte[]> imageData;
052
053        /** Type of image (one of BufferedImage.TYPE_*) */
054        private int type = BufferedImage.TYPE_INT_RGB;
055
056        /** Width of image */
057        private int width = 100;
058
059        /**
060         * Constructor.
061         * 
062         * @param width
063         *            Width of image
064         * @param height
065         *            Height of image
066         */
067        public RenderedDynamicImageResource(final int width, final int height)
068        {
069                this.width = width;
070                this.height = height;
071        }
072
073        /**
074         * Constructor.
075         * 
076         * @param width
077         *            Width of image
078         * @param height
079         *            Height of image
080         * @param format
081         *            The format of the image (jpg, png or gif)
082         */
083        public RenderedDynamicImageResource(final int width, final int height, String format)
084        {
085                super(format);
086                this.width = width;
087                this.height = height;
088        }
089
090        /**
091         * @return Returns the height.
092         */
093        public synchronized int getHeight()
094        {
095                return height;
096        }
097
098        /**
099         * @return Returns the type (one of BufferedImage.TYPE_*).
100         */
101        public synchronized int getType()
102        {
103                return type;
104        }
105
106        /**
107         * @return Returns the width.
108         */
109        public synchronized int getWidth()
110        {
111                return width;
112        }
113
114        /**
115         * Causes the image to be redrawn the next time its requested.
116         */
117        public synchronized void invalidate()
118        {
119                imageData = null;
120        }
121
122        /**
123         * @param height
124         *            The height to set.
125         */
126        public synchronized void setHeight(int height)
127        {
128                this.height = height;
129                invalidate();
130        }
131
132        /**
133         * @param type
134         *            The type to set (one of BufferedImage.TYPE_*).
135         */
136        public synchronized void setType(int type)
137        {
138                this.type = type;
139                invalidate();
140        }
141
142        /**
143         * @param width
144         *            The width to set.
145         */
146        public synchronized void setWidth(int width)
147        {
148                this.width = width;
149                invalidate();
150        }
151
152        @Override
153        protected byte[] getImageData(Attributes attributes)
154        {
155                // get image data is always called in sync block
156                byte[] data = null;
157                if (imageData != null)
158                {
159                        data = imageData.get();
160                }
161                if (data == null)
162                {
163                        data = render(attributes);
164                        imageData = new SoftReference<byte[]>(data);
165                        setLastModifiedTime(Instant.now());
166                }
167                return data;
168        }
169
170        /**
171         * Renders this image
172         * 
173         * @param attributes
174         *            the current request attributes
175         * @return The image data
176         */
177        protected byte[] render(final Attributes attributes)
178        {
179                while (true)
180                {
181                        final BufferedImage image = new BufferedImage(getWidth(), getHeight(), getType());
182                        if (render((Graphics2D)image.getGraphics(), attributes))
183                        {
184                                return toImageData(image);
185                        }
186                }
187        }
188
189        /**
190         * Override this method to provide your rendering code.
191         * 
192         * @param graphics
193         *            The graphics context to render on.
194         * @param attributes
195         *            the current request attributes
196         * @return {@code true} if the image was rendered. {@code false} if the image size was changed
197         *         by the rendering implementation and the image should be re-rendered at the new size.
198         */
199        protected abstract boolean render(Graphics2D graphics, final Attributes attributes);
200}