001/* 002 * Copyright 2010 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.skinfactory.readers; 017 018import java.awt.Dimension; 019import java.awt.image.BufferedImage; 020import java.io.ByteArrayInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.io.Serializable; 025import java.nio.file.Path; 026import java.util.Map; 027 028import javax.imageio.ImageIO; 029 030import org.apache.avalon.framework.parameters.Parameters; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.cocoon.ProcessingException; 034import org.apache.cocoon.caching.CacheableProcessingComponent; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Response; 037import org.apache.cocoon.environment.SourceResolver; 038import org.apache.cocoon.reading.ServiceableReader; 039import org.apache.commons.io.IOUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.excalibur.source.SourceValidity; 042import org.xml.sax.SAXException; 043 044import org.ametys.core.util.ImageHelper; 045import org.ametys.core.util.URIUtils; 046import org.ametys.core.util.path.PathSource; 047import org.ametys.plugins.skincommons.SkinEditionHelper; 048import org.ametys.web.skin.SkinModel; 049import org.ametys.web.skin.SkinModelsManager; 050 051import net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker; 052import net.coobird.thumbnailator.resizers.DefaultResizerFactory; 053 054/** 055 * Reader for resource of the skin 056 */ 057public class SkinResourceReader extends ServiceableReader implements CacheableProcessingComponent 058{ 059 private SkinModelsManager _modelsManager; 060 private SkinEditionHelper _skinHelper; 061 062 private PathSource _source; 063 064 private int _width; 065 private int _height; 066 private int _maxWidth; 067 private int _maxHeight; 068 069 @Override 070 public void service(ServiceManager sManager) throws ServiceException 071 { 072 super.service(sManager); 073 _modelsManager = (SkinModelsManager) sManager.lookup(SkinModelsManager.ROLE); 074 _skinHelper = (SkinEditionHelper) sManager.lookup(SkinEditionHelper.ROLE); 075 } 076 077 @Override 078 public void setup(SourceResolver sResolver, Map objModel, String src, Parameters par) throws ProcessingException, SAXException, IOException 079 { 080 super.setup(sResolver, objModel, src, par); 081 082 // parameters for image resizing 083 _width = par.getParameterAsInteger("width", 0); 084 _height = par.getParameterAsInteger("height", 0); 085 _maxWidth = par.getParameterAsInteger("maxWidth", 0); 086 _maxHeight = par.getParameterAsInteger("maxHeight", 0); 087 088 String path = par.getParameter("path", null); 089 assert path != null; 090 091 Path rootDir = null; 092 String modelName = par.getParameter("modelName", null); 093 if (StringUtils.isNotEmpty(modelName)) 094 { 095 SkinModel model = _modelsManager.getModel(modelName); 096 rootDir = model.getPath(); 097 _source = new PathSource("model", "model:" + modelName + "://" + path, rootDir.resolve(URIUtils.decode(path))); 098 } 099 else 100 { 101 String skinName = par.getParameter("skinName", null); 102 rootDir = _skinHelper.getTempDirectory(skinName); 103 _source = new PathSource("file", rootDir.resolve(URIUtils.decode(path))); 104 } 105 } 106 107 @Override 108 public Serializable getKey() 109 { 110 return _source.getFile().toAbsolutePath().toString() + "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth; 111 } 112 113 @Override 114 public SourceValidity getValidity() 115 { 116 return _source.getValidity(); 117 } 118 119 @Override 120 public long getLastModified() 121 { 122 return _source.getLastModified(); 123 } 124 125 @Override 126 public String getMimeType() 127 { 128 return _source.getMimeType(); 129 } 130 131 @Override 132 public void generate() throws IOException, SAXException, ProcessingException 133 { 134 String name = _source.getName(); 135 name = name.replaceAll("\\\\", "\\\\\\\\"); 136 name = name.replaceAll("\\\"", "\\\\\\\""); 137 138 Response response = ObjectModelHelper.getResponse(objectModel); 139 140 try (InputStream is = _source.getInputStream()) 141 { 142 if (_width > 0 || _height > 0) 143 { 144 // it's an image, which must be resized 145 int i = name.lastIndexOf('.'); 146 String format = name.substring(i + 1); 147 148 ImageHelper.generateThumbnail(is, out, format, _height, _width, 0, 0); 149 } 150 else if (_maxHeight > 0 || _maxWidth > 0) 151 { 152 // it's an image, which must be resized 153 int i = name.lastIndexOf('.'); 154 String format = name.substring(i + 1); 155 156 byte[] fileContent = IOUtils.toByteArray(is); 157 BufferedImage src = ImageHelper.read(new ByteArrayInputStream(fileContent)); 158 159 _generateThumbnail (out, format, src, fileContent, _maxHeight, _maxWidth); 160 } 161 else 162 { 163 response.setHeader("Content-Length", Long.toString(_source.getContentLength())); 164 IOUtils.copy(is, out); 165 } 166 } 167 catch (Exception e) 168 { 169 throw new ProcessingException("Unable to download file of uri " + _source.getURI(), e); 170 } 171 finally 172 { 173 IOUtils.closeQuietly(out); 174 } 175 } 176 177 @Override 178 public void recycle() 179 { 180 super.recycle(); 181 _source = null; 182 } 183 184 private void _generateThumbnail (OutputStream os, String format, BufferedImage src, byte[] fileContent, int maxHeight, int maxWidth) throws IOException 185 { 186 int srcHeight = src.getHeight(); 187 int srcWidth = src.getWidth(); 188 189 if (maxWidth == srcWidth && maxHeight == srcHeight) 190 { 191 IOUtils.write(fileContent, os); 192 return; 193 } 194 if (srcWidth < maxWidth && srcHeight < maxHeight) 195 { 196 // Image is too small : zoom them crop image to minHeight x minWidth dimension 197 _generateZoomAndCropImage (out, format, src, fileContent, maxHeight, maxWidth); 198 } 199 else 200 { 201 BufferedImage dest = ImageHelper.generateThumbnail(src, _height, _width, _maxHeight, _maxWidth); 202 if (src == dest) 203 { 204 // Thumbnail is equals to src image, means that the image is the same 205 // We'd rather like return the initial stream 206 IOUtils.write(fileContent, os); 207 } 208 else 209 { 210 ImageIO.write(dest, format, os); 211 } 212 } 213 } 214 215 private void _generateZoomAndCropImage (OutputStream os, String format, BufferedImage src, byte[] fileContent, int minHeight, int minWidth) throws IOException 216 { 217 int srcHeight = src.getHeight(); 218 int srcWidth = src.getWidth(); 219 220 int destHeight = 0; 221 int destWidth = 0; 222 223 Dimension srcDimension = new Dimension(srcWidth, srcHeight); 224 225 boolean keepAspectRatio = true; 226 227 if (srcWidth > srcHeight) 228 { 229 destHeight = minHeight; 230 231 // width is computed keeping ratio 232 destWidth = srcWidth * destHeight / srcHeight; 233 } 234 else 235 { 236 destWidth = minWidth; 237 238 // dest is computed keeping ratio 239 destHeight = srcHeight * destWidth / srcWidth; 240 } 241 242 Dimension thumbnailDimension = new Dimension(destWidth, destHeight); 243 244 245 BufferedImage thumbImage = new FixedSizeThumbnailMaker(destWidth, destHeight, keepAspectRatio, true) 246 .resizer(DefaultResizerFactory.getInstance().getResizer(srcDimension, thumbnailDimension)) 247 .imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB) 248 .make(src); 249 250 BufferedImage cropImage = _getCropImage (thumbImage, 0, 0, minHeight, minHeight); 251 252 if (src == cropImage) 253 { 254 // Thumbnail is equals to src image, means that the image is the same 255 // We'd rather like return the initial stream 256 IOUtils.write(fileContent, os); 257 } 258 else 259 { 260 ImageIO.write(cropImage, format, os); 261 } 262 } 263 264 private BufferedImage _getCropImage (BufferedImage src, int x, int y, int width, int height) 265 { 266 int srcHeight = src.getHeight(); 267 int srcWidth = src.getWidth(); 268 269 int w = width; 270 if (width + x > srcWidth) 271 { 272 w = srcWidth - x; 273 } 274 275 int h = height; 276 if (height + y > srcHeight) 277 { 278 h = srcHeight - y; 279 } 280 281 return src.getSubimage(x, y, w, h); 282 } 283}