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