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 out.flush(); 168 } 169 catch (Exception e) 170 { 171 throw new ProcessingException("Unable to download file of uri " + _source.getURI(), e); 172 } 173 } 174 175 @Override 176 public void recycle() 177 { 178 super.recycle(); 179 _source = null; 180 } 181 182 private void _generateThumbnail (OutputStream os, String format, BufferedImage src, byte[] fileContent, int maxHeight, int maxWidth) throws IOException 183 { 184 int srcHeight = src.getHeight(); 185 int srcWidth = src.getWidth(); 186 187 if (maxWidth == srcWidth && maxHeight == srcHeight) 188 { 189 IOUtils.write(fileContent, os); 190 return; 191 } 192 if (srcWidth < maxWidth && srcHeight < maxHeight) 193 { 194 // Image is too small : zoom them crop image to minHeight x minWidth dimension 195 _generateZoomAndCropImage (out, format, src, fileContent, maxHeight, maxWidth); 196 } 197 else 198 { 199 BufferedImage dest = ImageHelper.generateThumbnail(src, _height, _width, _maxHeight, _maxWidth); 200 if (src == dest) 201 { 202 // Thumbnail is equals to src image, means that the image is the same 203 // We'd rather like return the initial stream 204 IOUtils.write(fileContent, os); 205 } 206 else 207 { 208 ImageIO.write(dest, format, os); 209 } 210 } 211 } 212 213 private void _generateZoomAndCropImage (OutputStream os, String format, BufferedImage src, byte[] fileContent, int minHeight, int minWidth) throws IOException 214 { 215 int srcHeight = src.getHeight(); 216 int srcWidth = src.getWidth(); 217 218 int destHeight = 0; 219 int destWidth = 0; 220 221 Dimension srcDimension = new Dimension(srcWidth, srcHeight); 222 223 boolean keepAspectRatio = true; 224 225 if (srcWidth > srcHeight) 226 { 227 destHeight = minHeight; 228 229 // width is computed keeping ratio 230 destWidth = srcWidth * destHeight / srcHeight; 231 } 232 else 233 { 234 destWidth = minWidth; 235 236 // dest is computed keeping ratio 237 destHeight = srcHeight * destWidth / srcWidth; 238 } 239 240 Dimension thumbnailDimension = new Dimension(destWidth, destHeight); 241 242 243 BufferedImage thumbImage = new FixedSizeThumbnailMaker(destWidth, destHeight, keepAspectRatio, true) 244 .resizer(DefaultResizerFactory.getInstance().getResizer(srcDimension, thumbnailDimension)) 245 .imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB) 246 .make(src); 247 248 BufferedImage cropImage = _getCropImage (thumbImage, 0, 0, minHeight, minHeight); 249 250 if (src == cropImage) 251 { 252 // Thumbnail is equals to src image, means that the image is the same 253 // We'd rather like return the initial stream 254 IOUtils.write(fileContent, os); 255 } 256 else 257 { 258 ImageIO.write(cropImage, format, os); 259 } 260 } 261 262 private BufferedImage _getCropImage (BufferedImage src, int x, int y, int width, int height) 263 { 264 int srcHeight = src.getHeight(); 265 int srcWidth = src.getWidth(); 266 267 int w = width; 268 if (width + x > srcWidth) 269 { 270 w = srcWidth - x; 271 } 272 273 int h = height; 274 if (height + y > srcHeight) 275 { 276 h = srcHeight - y; 277 } 278 279 return src.getSubimage(x, y, w, h); 280 } 281}