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