001
014
015 package com.liferay.portal.image;
016
017 import com.liferay.portal.kernel.concurrent.FutureConverter;
018 import com.liferay.portal.kernel.image.ImageBag;
019 import com.liferay.portal.kernel.image.ImageMagick;
020 import com.liferay.portal.kernel.image.ImageTool;
021 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
022 import com.liferay.portal.kernel.log.Log;
023 import com.liferay.portal.kernel.log.LogFactoryUtil;
024 import com.liferay.portal.kernel.security.pacl.DoPrivileged;
025 import com.liferay.portal.kernel.util.ArrayUtil;
026 import com.liferay.portal.kernel.util.JavaDetector;
027 import com.liferay.portal.kernel.util.PropsKeys;
028 import com.liferay.portal.kernel.util.StringUtil;
029 import com.liferay.portal.model.Image;
030 import com.liferay.portal.model.impl.ImageImpl;
031 import com.liferay.portal.util.FileImpl;
032 import com.liferay.portal.util.PropsUtil;
033 import com.liferay.portal.util.PropsValues;
034
035 import java.awt.AlphaComposite;
036 import java.awt.Graphics;
037 import java.awt.Graphics2D;
038 import java.awt.GraphicsConfiguration;
039 import java.awt.Rectangle;
040 import java.awt.image.BufferedImage;
041 import java.awt.image.ColorModel;
042 import java.awt.image.DataBuffer;
043 import java.awt.image.IndexColorModel;
044 import java.awt.image.RenderedImage;
045 import java.awt.image.SampleModel;
046 import java.awt.image.WritableRaster;
047
048 import java.io.ByteArrayInputStream;
049 import java.io.File;
050 import java.io.IOException;
051 import java.io.InputStream;
052 import java.io.OutputStream;
053
054 import java.util.Arrays;
055 import java.util.Hashtable;
056 import java.util.Iterator;
057 import java.util.LinkedList;
058 import java.util.Queue;
059 import java.util.concurrent.Future;
060
061 import javax.imageio.ImageIO;
062 import javax.imageio.ImageReader;
063 import javax.imageio.stream.ImageInputStream;
064
065 import net.jmge.gif.Gif89Encoder;
066
067 import org.im4java.core.IMOperation;
068
069
074 @DoPrivileged
075 public class ImageToolImpl implements ImageTool {
076
077 public static ImageTool getInstance() {
078 return _instance;
079 }
080
081 public void afterPropertiesSet() {
082 ClassLoader classLoader = getClass().getClassLoader();
083
084 try {
085 InputStream is = classLoader.getResourceAsStream(
086 PropsUtil.get(PropsKeys.IMAGE_DEFAULT_SPACER));
087
088 if (is == null) {
089 _log.error("Default spacer is not available");
090 }
091
092 _defaultSpacer = getImage(is);
093 }
094 catch (Exception e) {
095 _log.error(
096 "Unable to configure the default spacer: " + e.getMessage());
097 }
098
099 try {
100 InputStream is = classLoader.getResourceAsStream(
101 PropsUtil.get(PropsKeys.IMAGE_DEFAULT_COMPANY_LOGO));
102
103 if (is == null) {
104 _log.error("Default company logo is not available");
105 }
106
107 _defaultCompanyLogo = getImage(is);
108 }
109 catch (Exception e) {
110 _log.error(
111 "Unable to configure the default company logo: " +
112 e.getMessage());
113 }
114
115 try {
116 InputStream is = classLoader.getResourceAsStream(
117 PropsUtil.get(PropsKeys.IMAGE_DEFAULT_ORGANIZATION_LOGO));
118
119 if (is == null) {
120 _log.error("Default organization logo is not available");
121 }
122
123 _defaultOrganizationLogo = getImage(is);
124 }
125 catch (Exception e) {
126 _log.error(
127 "Unable to configure the default organization logo: " +
128 e.getMessage());
129 }
130
131 try {
132 InputStream is = classLoader.getResourceAsStream(
133 PropsUtil.get(PropsKeys.IMAGE_DEFAULT_USER_FEMALE_PORTRAIT));
134
135 if (is == null) {
136 _log.error("Default user female portrait is not available");
137 }
138
139 _defaultUserFemalePortrait = getImage(is);
140 }
141 catch (Exception e) {
142 _log.error(
143 "Unable to configure the default user female portrait: " +
144 e.getMessage());
145 }
146
147 try {
148 InputStream is = classLoader.getResourceAsStream(
149 PropsUtil.get(PropsKeys.IMAGE_DEFAULT_USER_MALE_PORTRAIT));
150
151 if (is == null) {
152 _log.error("Default user male portrait is not available");
153 }
154
155 _defaultUserMalePortrait = getImage(is);
156 }
157 catch (Exception e) {
158 _log.error(
159 "Unable to configure the default user male portrait: " +
160 e.getMessage());
161 }
162 }
163
164 @Override
165 public Future<RenderedImage> convertCMYKtoRGB(
166 byte[] bytes, final String type) {
167
168 ImageMagick imageMagick = getImageMagick();
169
170 if (!imageMagick.isEnabled()) {
171 return null;
172 }
173
174 File inputFile = _fileUtil.createTempFile(type);
175 final File outputFile = _fileUtil.createTempFile(type);
176
177 try {
178 _fileUtil.write(inputFile, bytes);
179
180 IMOperation imOperation = new IMOperation();
181
182 imOperation.addRawArgs("-format", "%[colorspace]");
183 imOperation.addImage(inputFile.getPath());
184
185 String[] output = imageMagick.identify(imOperation.getCmdArgs());
186
187 if ((output.length == 1) &&
188 StringUtil.equalsIgnoreCase(output[0], "CMYK")) {
189
190 if (_log.isInfoEnabled()) {
191 _log.info("The image is in the CMYK colorspace");
192 }
193
194 imOperation = new IMOperation();
195
196 imOperation.addRawArgs("-colorspace", "RGB");
197 imOperation.addImage(inputFile.getPath());
198 imOperation.addImage(outputFile.getPath());
199
200 Future<Object> future = (Future<Object>)imageMagick.convert(
201 imOperation.getCmdArgs());
202
203 return new FutureConverter<RenderedImage, Object>(future) {
204
205 @Override
206 protected RenderedImage convert(Object obj) {
207 RenderedImage renderedImage = null;
208
209 try {
210 ImageBag imageBag = read(
211 _fileUtil.getBytes(outputFile));
212
213 renderedImage = imageBag.getRenderedImage();
214 }
215 catch (IOException ioe) {
216 if (_log.isDebugEnabled()) {
217 _log.debug("Unable to convert " + type, ioe);
218 }
219 }
220
221 return renderedImage;
222 }
223
224 };
225 }
226 }
227 catch (Exception e) {
228 if (_log.isErrorEnabled()) {
229 _log.error(e, e);
230 }
231 }
232 finally {
233 _fileUtil.delete(inputFile);
234 _fileUtil.delete(outputFile);
235 }
236
237 return null;
238 }
239
240 @Override
241 public BufferedImage convertImageType(BufferedImage sourceImage, int type) {
242 BufferedImage targetImage = new BufferedImage(
243 sourceImage.getWidth(), sourceImage.getHeight(), type);
244
245 Graphics2D graphics = targetImage.createGraphics();
246
247 graphics.drawRenderedImage(sourceImage, null);
248
249 graphics.dispose();
250
251 return targetImage;
252 }
253
254 @Override
255 public RenderedImage crop(
256 RenderedImage renderedImage, int height, int width, int x, int y) {
257
258 Rectangle rectangle = new Rectangle(x, y, width, height);
259
260 Rectangle croppedRectangle = rectangle.intersection(
261 new Rectangle(renderedImage.getWidth(), renderedImage.getHeight()));
262
263 BufferedImage bufferedImage = getBufferedImage(renderedImage);
264
265 return bufferedImage.getSubimage(
266 croppedRectangle.x, croppedRectangle.y, croppedRectangle.width,
267 croppedRectangle.height);
268 }
269
270 @Override
271 public void encodeGIF(RenderedImage renderedImage, OutputStream os)
272 throws IOException {
273
274 if (JavaDetector.isJDK6()) {
275 ImageIO.write(renderedImage, TYPE_GIF, os);
276 }
277 else {
278 BufferedImage bufferedImage = getBufferedImage(renderedImage);
279
280 if (!(bufferedImage.getColorModel() instanceof IndexColorModel)) {
281 bufferedImage = convertImageType(
282 bufferedImage, BufferedImage.TYPE_BYTE_INDEXED);
283 }
284
285 Gif89Encoder encoder = new Gif89Encoder(bufferedImage);
286
287 encoder.encode(os);
288 }
289 }
290
291 @Override
292 public void encodeWBMP(RenderedImage renderedImage, OutputStream os)
293 throws IOException {
294
295 BufferedImage bufferedImage = getBufferedImage(renderedImage);
296
297 SampleModel sampleModel = bufferedImage.getSampleModel();
298
299 int type = sampleModel.getDataType();
300
301 if ((bufferedImage.getType() != BufferedImage.TYPE_BYTE_BINARY) ||
302 (type < DataBuffer.TYPE_BYTE) || (type > DataBuffer.TYPE_INT) ||
303 (sampleModel.getNumBands() != 1) ||
304 (sampleModel.getSampleSize(0) != 1)) {
305
306 BufferedImage binaryImage = new BufferedImage(
307 bufferedImage.getWidth(), bufferedImage.getHeight(),
308 BufferedImage.TYPE_BYTE_BINARY);
309
310 Graphics graphics = binaryImage.getGraphics();
311
312 graphics.drawImage(bufferedImage, 0, 0, null);
313
314 renderedImage = binaryImage;
315 }
316
317 if (!ImageIO.write(renderedImage, "wbmp", os)) {
318
319
320
321 os.write(0);
322 os.write(0);
323 os.write(toMultiByte(bufferedImage.getWidth()));
324 os.write(toMultiByte(bufferedImage.getHeight()));
325
326 DataBuffer dataBuffer = bufferedImage.getData().getDataBuffer();
327
328 int size = dataBuffer.getSize();
329
330 for (int i = 0; i < size; i++) {
331 os.write((byte)dataBuffer.getElem(i));
332 }
333 }
334 }
335
336 @Override
337 public BufferedImage getBufferedImage(RenderedImage renderedImage) {
338 if (renderedImage instanceof BufferedImage) {
339 return (BufferedImage)renderedImage;
340 }
341
342 ColorModel colorModel = renderedImage.getColorModel();
343
344 WritableRaster writableRaster =
345 colorModel.createCompatibleWritableRaster(
346 renderedImage.getWidth(), renderedImage.getHeight());
347
348 Hashtable<String, Object> properties = new Hashtable<String, Object>();
349
350 String[] keys = renderedImage.getPropertyNames();
351
352 if (!ArrayUtil.isEmpty(keys)) {
353 for (String key : keys) {
354 properties.put(key, renderedImage.getProperty(key));
355 }
356 }
357
358 BufferedImage bufferedImage = new BufferedImage(
359 colorModel, writableRaster, colorModel.isAlphaPremultiplied(),
360 properties);
361
362 renderedImage.copyData(writableRaster);
363
364 return bufferedImage;
365 }
366
367 @Override
368 public byte[] getBytes(RenderedImage renderedImage, String contentType)
369 throws IOException {
370
371 UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();
372
373 write(renderedImage, contentType, baos);
374
375 return baos.toByteArray();
376 }
377
378 @Override
379 public Image getDefaultCompanyLogo() {
380 return _defaultCompanyLogo;
381 }
382
383 @Override
384 public Image getDefaultOrganizationLogo() {
385 return _defaultOrganizationLogo;
386 }
387
388 @Override
389 public Image getDefaultSpacer() {
390 return _defaultSpacer;
391 }
392
393 @Override
394 public Image getDefaultUserFemalePortrait() {
395 return _defaultUserFemalePortrait;
396 }
397
398 @Override
399 public Image getDefaultUserMalePortrait() {
400 return _defaultUserMalePortrait;
401 }
402
403 @Override
404 public Image getImage(byte[] bytes) throws IOException {
405 if (bytes == null) {
406 return null;
407 }
408
409 ImageBag imageBag = read(bytes);
410
411 RenderedImage renderedImage = imageBag.getRenderedImage();
412
413 if (renderedImage == null) {
414 throw new IOException("Unable to decode image");
415 }
416
417 String type = imageBag.getType();
418
419 int height = renderedImage.getHeight();
420 int width = renderedImage.getWidth();
421 int size = bytes.length;
422
423 Image image = new ImageImpl();
424
425 image.setTextObj(bytes);
426 image.setType(type);
427 image.setHeight(height);
428 image.setWidth(width);
429 image.setSize(size);
430
431 return image;
432 }
433
434 @Override
435 public Image getImage(File file) throws IOException {
436 byte[] bytes = _fileUtil.getBytes(file);
437
438 return getImage(bytes);
439 }
440
441 @Override
442 public Image getImage(InputStream is) throws IOException {
443 byte[] bytes = _fileUtil.getBytes(is, -1, true);
444
445 return getImage(bytes);
446 }
447
448 @Override
449 public Image getImage(InputStream is, boolean cleanUpStream)
450 throws IOException {
451
452 byte[] bytes = _fileUtil.getBytes(is, -1, cleanUpStream);
453
454 return getImage(bytes);
455 }
456
457 @Override
458 public boolean isNullOrDefaultSpacer(byte[] bytes) {
459 if (ArrayUtil.isEmpty(bytes) ||
460 Arrays.equals(bytes, getDefaultSpacer().getTextObj())) {
461
462 return true;
463 }
464 else {
465 return false;
466 }
467 }
468
469 @Override
470 public ImageBag read(byte[] bytes) throws IOException {
471 String formatName = null;
472 ImageInputStream imageInputStream = null;
473 Queue<ImageReader> imageReaders = new LinkedList<ImageReader>();
474 RenderedImage renderedImage = null;
475
476 try {
477 boolean firstImageReader = true;
478
479 imageInputStream = ImageIO.createImageInputStream(
480 new ByteArrayInputStream(bytes));
481
482 Iterator<ImageReader> iterator = ImageIO.getImageReaders(
483 imageInputStream);
484
485 while (iterator.hasNext()) {
486 ImageReader imageReader = iterator.next();
487
488 imageReaders.offer(imageReader);
489
490 if (firstImageReader) {
491 imageReader.setInput(imageInputStream);
492
493 renderedImage = imageReader.read(0);
494
495 formatName = imageReader.getFormatName();
496
497 firstImageReader = false;
498 }
499 }
500 }
501 finally {
502 while (!imageReaders.isEmpty()) {
503 ImageReader imageReader = imageReaders.poll();
504
505 imageReader.dispose();
506 }
507
508 if (imageInputStream != null) {
509 imageInputStream.close();
510 }
511 }
512
513 formatName = StringUtil.toLowerCase(formatName);
514
515 String type = TYPE_JPEG;
516
517 if (formatName.contains(TYPE_BMP)) {
518 type = TYPE_BMP;
519 }
520 else if (formatName.contains(TYPE_GIF)) {
521 type = TYPE_GIF;
522 }
523 else if (formatName.contains("jpeg") || type.equals("jpeg")) {
524 type = TYPE_JPEG;
525 }
526 else if (formatName.contains(TYPE_PNG)) {
527 type = TYPE_PNG;
528 }
529 else if (formatName.contains(TYPE_TIFF)) {
530 type = TYPE_TIFF;
531 }
532 else {
533 throw new IllegalArgumentException(type + " is not supported");
534 }
535
536 return new ImageBag(renderedImage, type);
537 }
538
539 @Override
540 public ImageBag read(File file) throws IOException {
541 return read(_fileUtil.getBytes(file));
542 }
543
544 @Override
545 public ImageBag read(InputStream inputStream) throws IOException {
546 return read(_fileUtil.getBytes(inputStream));
547 }
548
549 @Override
550 public RenderedImage scale(RenderedImage renderedImage, int width) {
551 if (width <= 0) {
552 return renderedImage;
553 }
554
555 int imageHeight = renderedImage.getHeight();
556 int imageWidth = renderedImage.getWidth();
557
558 double factor = (double)width / imageWidth;
559
560 int scaledHeight = (int)Math.round(factor * imageHeight);
561 int scaledWidth = width;
562
563 return doScale(renderedImage, scaledHeight, scaledWidth);
564 }
565
566 @Override
567 public RenderedImage scale(
568 RenderedImage renderedImage, int maxHeight, int maxWidth) {
569
570 int imageHeight = renderedImage.getHeight();
571 int imageWidth = renderedImage.getWidth();
572
573 if (maxHeight == 0) {
574 maxHeight = imageHeight;
575 }
576
577 if (maxWidth == 0) {
578 maxWidth = imageWidth;
579 }
580
581 if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) {
582 return renderedImage;
583 }
584
585 double factor = Math.min(
586 (double)maxHeight / imageHeight, (double)maxWidth / imageWidth);
587
588 int scaledHeight = Math.max(1, (int)Math.round(factor * imageHeight));
589 int scaledWidth = Math.max(1, (int)Math.round(factor * imageWidth));
590
591 return doScale(renderedImage, scaledHeight, scaledWidth);
592 }
593
594 @Override
595 public void write(
596 RenderedImage renderedImage, String contentType, OutputStream os)
597 throws IOException {
598
599 if (contentType.contains(TYPE_BMP)) {
600 ImageIO.write(renderedImage, "bmp", os);
601 }
602 else if (contentType.contains(TYPE_GIF)) {
603 encodeGIF(renderedImage, os);
604 }
605 else if (contentType.contains(TYPE_JPEG) ||
606 contentType.contains("jpeg")) {
607
608 ImageIO.write(renderedImage, "jpeg", os);
609 }
610 else if (contentType.contains(TYPE_PNG)) {
611 ImageIO.write(renderedImage, TYPE_PNG, os);
612 }
613 else if (contentType.contains(TYPE_TIFF) ||
614 contentType.contains("tif")) {
615
616 ImageIO.write(renderedImage, "tiff", os);
617 }
618 }
619
620 protected RenderedImage doScale(
621 RenderedImage renderedImage, int scaledHeight, int scaledWidth) {
622
623
624
625 BufferedImage originalBufferedImage = getBufferedImage(renderedImage);
626
627 ColorModel originalColorModel = originalBufferedImage.getColorModel();
628
629 Graphics2D originalGraphics2D = originalBufferedImage.createGraphics();
630
631 if (originalColorModel.hasAlpha()) {
632 originalGraphics2D.setComposite(AlphaComposite.Src);
633 }
634
635 GraphicsConfiguration originalGraphicsConfiguration =
636 originalGraphics2D.getDeviceConfiguration();
637
638 BufferedImage scaledBufferedImage =
639 originalGraphicsConfiguration.createCompatibleImage(
640 scaledWidth, scaledHeight,
641 originalBufferedImage.getTransparency());
642
643 Graphics scaledGraphics = scaledBufferedImage.getGraphics();
644
645 scaledGraphics.drawImage(
646 originalBufferedImage.getScaledInstance(
647 scaledWidth, scaledHeight, java.awt.Image.SCALE_SMOOTH),
648 0, 0, null);
649
650 originalGraphics2D.dispose();
651
652 return scaledBufferedImage;
653 }
654
655 protected ImageMagick getImageMagick() {
656 if (_imageMagick == null) {
657 _imageMagick = ImageMagickImpl.getInstance();
658
659 _imageMagick.reset();
660 }
661
662 return _imageMagick;
663 }
664
665 protected byte[] toMultiByte(int intValue) {
666 int numBits = 32;
667 int mask = 0x80000000;
668
669 while ((mask != 0) && ((intValue & mask) == 0)) {
670 numBits--;
671 mask >>>= 1;
672 }
673
674 int numBitsLeft = numBits;
675 byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];
676
677 int maxIndex = multiBytes.length - 1;
678
679 for (int b = 0; b <= maxIndex; b++) {
680 multiBytes[b] = (byte)((intValue >>> ((maxIndex - b) * 7)) & 0x7f);
681
682 if (b != maxIndex) {
683 multiBytes[b] |= (byte)0x80;
684 }
685 }
686
687 return multiBytes;
688 }
689
690 private ImageToolImpl() {
691 ImageIO.setUseCache(PropsValues.IMAGE_IO_USE_DISK_CACHE);
692 }
693
694 private static Log _log = LogFactoryUtil.getLog(ImageToolImpl.class);
695
696 private static ImageTool _instance = new ImageToolImpl();
697
698 private static FileImpl _fileUtil = FileImpl.getInstance();
699 private static ImageMagick _imageMagick;
700
701 private Image _defaultCompanyLogo;
702 private Image _defaultOrganizationLogo;
703 private Image _defaultSpacer;
704 private Image _defaultUserFemalePortrait;
705 private Image _defaultUserMalePortrait;
706
707 }