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