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.extensions.markup.html.image.resource;
018
019import java.awt.Graphics2D;
020import java.awt.RenderingHints;
021import java.awt.image.BufferedImage;
022import java.io.ByteArrayInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.time.Instant;
026import javax.imageio.ImageIO;
027import org.apache.wicket.WicketRuntimeException;
028import org.apache.wicket.request.resource.DynamicImageResource;
029import org.apache.wicket.request.resource.IResource;
030import org.apache.wicket.response.ByteArrayResponse;
031import org.apache.wicket.util.io.IOUtils;
032import org.apache.wicket.util.lang.Args;
033
034/**
035 * Image resource that dynamically scales the given original resource to a thumbnail. It is scaled
036 * either using the given maxSize as width or height, depending on its shape. If both the width and
037 * height are less than maxSize, no scaling is performed.
038 * 
039 * @author Eelco Hillenius
040 * @author Eugene Kamenev
041 */
042
043public class ThumbnailImageResource extends DynamicImageResource
044{
045        private static final long serialVersionUID = 1L;
046
047        /** the unscaled, original image resource. */
048        private final IResource unscaledImageResource;
049
050        /** maximum size (width or height) for resize operation. */
051        private final int maxSize;
052
053        /** the cached byte array of the thumbnail. */
054        private transient byte[] thumbnail;
055
056        /**
057         * Construct.
058         * 
059         * @param unscaledImageResource
060         *            the unscaled, original image resource. Must be not null
061         * @param maxSize
062         *            maximum size (width or height) for resize operation
063         */
064        public ThumbnailImageResource(final IResource unscaledImageResource, final int maxSize)
065        {
066                Args.notNull(unscaledImageResource, "unscaledImageResource");
067                
068                this.unscaledImageResource = unscaledImageResource;
069                this.maxSize = maxSize;
070        }
071
072        /**
073         * @return The image data for this dynamic image
074         */
075        @Override
076        protected byte[] getImageData(final Attributes attributes)
077        {
078                if (thumbnail == null)
079                {
080                        final BufferedImage image = getScaledImageInstance(attributes);
081                        thumbnail = toImageData(image);
082                        setLastModifiedTime(Instant.now());
083                }
084                return thumbnail;
085        }
086
087        /**
088         * get resized image instance.
089         * 
090         * @param attributes
091         * 
092         * @return BufferedImage
093         */
094        protected BufferedImage getScaledImageInstance(final Attributes attributes)
095        {
096                InputStream is = null;
097                BufferedImage originalImage = null;
098                try
099                {
100                        // read original image
101                        ByteArrayResponse byteResponse = new ByteArrayResponse();
102                        Attributes dispatchAttributes = new Attributes(attributes.getRequest(), byteResponse, attributes.getParameters());
103                        unscaledImageResource.respond(dispatchAttributes);
104                        is = new ByteArrayInputStream(byteResponse.getBytes());
105                        originalImage = ImageIO.read(is);
106                        if (originalImage == null)
107                        {
108                                throw new IOException("Unable to read unscaled image");
109                        }
110                }
111                catch (IOException e)
112                {
113                        throw new WicketRuntimeException(e);
114                }
115                finally
116                {
117                        IOUtils.closeQuietly(is);
118                }
119
120                int originalWidth = originalImage.getWidth();
121                int originalHeight = originalImage.getHeight();
122
123                if ((originalWidth > maxSize) || (originalHeight > maxSize))
124                {
125                        final int newWidth;
126                        final int newHeight;
127
128                        if (originalWidth > originalHeight)
129                        {
130                                newWidth = maxSize;
131                                newHeight = (maxSize * originalHeight) / originalWidth;
132                        }
133                        else
134                        {
135                                newWidth = (maxSize * originalWidth) / originalHeight;
136                                newHeight = maxSize;
137                        }
138
139                        // http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
140                        BufferedImage dimg = new BufferedImage(newWidth, newHeight, originalImage.getType());
141                        Graphics2D g = dimg.createGraphics();
142                        try
143                        {
144                                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
145                                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
146                                g.drawImage(originalImage, 0, 0, newWidth, newHeight, 0, 0, originalWidth,
147                                        originalHeight, null);
148                        }
149                        finally
150                        {
151                                g.dispose();
152                        }
153
154                        return dimg;
155                }
156
157                // no need for resizing
158                return originalImage;
159        }
160
161}