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