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.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
034 import com.sun.media.jai.codec.ImageCodec;
035 import com.sun.media.jai.codec.ImageDecoder;
036 import com.sun.media.jai.codec.ImageEncoder;
037
038 import java.awt.Graphics;
039 import java.awt.Graphics2D;
040 import java.awt.image.BufferedImage;
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.File;
048 import java.io.IOException;
049 import java.io.InputStream;
050 import java.io.OutputStream;
051
052 import java.util.Arrays;
053 import java.util.Enumeration;
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
061 import javax.media.jai.RenderedImageAdapter;
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 RenderedImageAdapter adapter = new RenderedImageAdapter(renderedImage);
302
303 return adapter.getAsBufferedImage();
304 }
305
306 @Override
307 public byte[] getBytes(RenderedImage renderedImage, String contentType)
308 throws IOException {
309
310 UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();
311
312 write(renderedImage, contentType, baos);
313
314 return baos.toByteArray();
315 }
316
317 @Override
318 public Image getDefaultCompanyLogo() {
319 return _defaultCompanyLogo;
320 }
321
322 @Override
323 public Image getDefaultOrganizationLogo() {
324 return _defaultOrganizationLogo;
325 }
326
327 @Override
328 public Image getDefaultSpacer() {
329 return _defaultSpacer;
330 }
331
332 @Override
333 public Image getDefaultUserFemalePortrait() {
334 return _defaultUserFemalePortrait;
335 }
336
337 @Override
338 public Image getDefaultUserMalePortrait() {
339 return _defaultUserMalePortrait;
340 }
341
342 @Override
343 public Image getImage(byte[] bytes) throws IOException {
344 if (bytes == null) {
345 return null;
346 }
347
348 ImageBag imageBag = read(bytes);
349
350 RenderedImage renderedImage = imageBag.getRenderedImage();
351
352 if (renderedImage == null) {
353 throw new IOException("Unable to decode image");
354 }
355
356 String type = imageBag.getType();
357
358 int height = renderedImage.getHeight();
359 int width = renderedImage.getWidth();
360 int size = bytes.length;
361
362 Image image = new ImageImpl();
363
364 image.setTextObj(bytes);
365 image.setType(type);
366 image.setHeight(height);
367 image.setWidth(width);
368 image.setSize(size);
369
370 return image;
371 }
372
373 @Override
374 public Image getImage(File file) throws IOException {
375 byte[] bytes = _fileUtil.getBytes(file);
376
377 return getImage(bytes);
378 }
379
380 @Override
381 public Image getImage(InputStream is) throws IOException {
382 byte[] bytes = _fileUtil.getBytes(is, -1, true);
383
384 return getImage(bytes);
385 }
386
387 @Override
388 public Image getImage(InputStream is, boolean cleanUpStream)
389 throws IOException {
390
391 byte[] bytes = _fileUtil.getBytes(is, -1, cleanUpStream);
392
393 return getImage(bytes);
394 }
395
396 @Override
397 public boolean isNullOrDefaultSpacer(byte[] bytes) {
398 if (ArrayUtil.isEmpty(bytes) ||
399 Arrays.equals(bytes, getDefaultSpacer().getTextObj())) {
400
401 return true;
402 }
403 else {
404 return false;
405 }
406 }
407
408 @Override
409 public ImageBag read(byte[] bytes) {
410 RenderedImage renderedImage = null;
411 String type = TYPE_NOT_AVAILABLE;
412
413 Enumeration<ImageCodec> enu = ImageCodec.getCodecs();
414
415 while (enu.hasMoreElements()) {
416 ImageCodec codec = enu.nextElement();
417
418 if (codec.isFormatRecognized(bytes)) {
419 type = codec.getFormatName();
420
421 renderedImage = read(bytes, type);
422
423 break;
424 }
425 }
426
427 if (type.equals("jpeg")) {
428 type = TYPE_JPEG;
429 }
430
431 return new ImageBag(renderedImage, type);
432 }
433
434 @Override
435 public ImageBag read(File file) throws IOException {
436 return read(_fileUtil.getBytes(file));
437 }
438
439 @Override
440 public ImageBag read(InputStream inputStream) throws IOException {
441 return read(_fileUtil.getBytes(inputStream));
442 }
443
444 @Override
445 public RenderedImage scale(RenderedImage renderedImage, int width) {
446 if (width <= 0) {
447 return renderedImage;
448 }
449
450 int imageHeight = renderedImage.getHeight();
451 int imageWidth = renderedImage.getWidth();
452
453 double factor = (double)width / imageWidth;
454
455 int scaledHeight = (int)(factor * imageHeight);
456 int scaledWidth = width;
457
458 BufferedImage bufferedImage = getBufferedImage(renderedImage);
459
460 int type = bufferedImage.getType();
461
462 if (type == 0) {
463 type = BufferedImage.TYPE_INT_ARGB;
464 }
465
466 BufferedImage scaledBufferedImage = new BufferedImage(
467 scaledWidth, scaledHeight, type);
468
469 Graphics graphics = scaledBufferedImage.getGraphics();
470
471 java.awt.Image scaledImage = bufferedImage.getScaledInstance(
472 scaledWidth, scaledHeight, java.awt.Image.SCALE_SMOOTH);
473
474 graphics.drawImage(scaledImage, 0, 0, null);
475
476 return scaledBufferedImage;
477 }
478
479 @Override
480 public RenderedImage scale(
481 RenderedImage renderedImage, int maxHeight, int maxWidth) {
482
483 int imageHeight = renderedImage.getHeight();
484 int imageWidth = renderedImage.getWidth();
485
486 if (maxHeight == 0) {
487 maxHeight = imageHeight;
488 }
489
490 if (maxWidth == 0) {
491 maxWidth = imageWidth;
492 }
493
494 if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) {
495 return renderedImage;
496 }
497
498 double factor = Math.min(
499 (double)maxHeight / imageHeight, (double)maxWidth / imageWidth);
500
501 int scaledHeight = Math.max(1, (int)(factor * imageHeight));
502 int scaledWidth = Math.max(1, (int)(factor * imageWidth));
503
504 BufferedImage bufferedImage = getBufferedImage(renderedImage);
505
506 int type = bufferedImage.getType();
507
508 if (type == 0) {
509 type = BufferedImage.TYPE_INT_ARGB;
510 }
511
512 BufferedImage scaledBufferedImage = null;
513
514 if ((type == BufferedImage.TYPE_BYTE_BINARY) ||
515 (type == BufferedImage.TYPE_BYTE_INDEXED)) {
516
517 IndexColorModel indexColorModel =
518 (IndexColorModel)bufferedImage.getColorModel();
519
520 BufferedImage tempBufferedImage = new BufferedImage(
521 1, 1, type, indexColorModel);
522
523 int bits = indexColorModel.getPixelSize();
524 int size = indexColorModel.getMapSize();
525
526 byte[] reds = new byte[size];
527
528 indexColorModel.getReds(reds);
529
530 byte[] greens = new byte[size];
531
532 indexColorModel.getGreens(greens);
533
534 byte[] blues = new byte[size];
535
536 indexColorModel.getBlues(blues);
537
538 WritableRaster writableRaster = tempBufferedImage.getRaster();
539
540 int pixel = writableRaster.getSample(0, 0, 0);
541
542 IndexColorModel scaledIndexColorModel = new IndexColorModel(
543 bits, size, reds, greens, blues, pixel);
544
545 scaledBufferedImage = new BufferedImage(
546 scaledWidth, scaledHeight, type, scaledIndexColorModel);
547 }
548 else {
549 scaledBufferedImage = new BufferedImage(
550 scaledWidth, scaledHeight, type);
551 }
552
553 Graphics graphics = scaledBufferedImage.getGraphics();
554
555 java.awt.Image scaledImage = bufferedImage.getScaledInstance(
556 scaledWidth, scaledHeight, java.awt.Image.SCALE_SMOOTH);
557
558 graphics.drawImage(scaledImage, 0, 0, null);
559
560 return scaledBufferedImage;
561 }
562
563 @Override
564 public void write(
565 RenderedImage renderedImage, String contentType, OutputStream os)
566 throws IOException {
567
568 if (contentType.contains(TYPE_BMP)) {
569 ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
570 TYPE_BMP, os, null);
571
572 imageEncoder.encode(renderedImage);
573 }
574 else if (contentType.contains(TYPE_GIF)) {
575 encodeGIF(renderedImage, os);
576 }
577 else if (contentType.contains(TYPE_JPEG) ||
578 contentType.contains("jpeg")) {
579
580 ImageIO.write(renderedImage, "jpeg", os);
581 }
582 else if (contentType.contains(TYPE_PNG)) {
583 ImageIO.write(renderedImage, TYPE_PNG, os);
584 }
585 else if (contentType.contains(TYPE_TIFF) ||
586 contentType.contains("tif")) {
587
588 ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
589 TYPE_TIFF, os, null);
590
591 imageEncoder.encode(renderedImage);
592 }
593 }
594
595 protected ImageMagick getImageMagick() {
596 if (_imageMagick == null) {
597 _imageMagick = ImageMagickImpl.getInstance();
598
599 _imageMagick.reset();
600 }
601
602 return _imageMagick;
603 }
604
605 protected RenderedImage read(byte[] bytes, String type) {
606 RenderedImage renderedImage = null;
607
608 try {
609 if (type.equals(TYPE_JPEG)) {
610 type = "jpeg";
611 }
612
613 ImageDecoder imageDecoder = ImageCodec.createImageDecoder(
614 type, new UnsyncByteArrayInputStream(bytes), null);
615
616 renderedImage = imageDecoder.decodeAsRenderedImage();
617 }
618 catch (IOException ioe) {
619 if (_log.isDebugEnabled()) {
620 _log.debug(type + ": " + ioe.getMessage());
621 }
622 }
623
624 return renderedImage;
625 }
626
627 protected byte[] toMultiByte(int intValue) {
628 int numBits = 32;
629 int mask = 0x80000000;
630
631 while ((mask != 0) && ((intValue & mask) == 0)) {
632 numBits--;
633 mask >>>= 1;
634 }
635
636 int numBitsLeft = numBits;
637 byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];
638
639 int maxIndex = multiBytes.length - 1;
640
641 for (int b = 0; b <= maxIndex; b++) {
642 multiBytes[b] = (byte)((intValue >>> ((maxIndex - b) * 7)) & 0x7f);
643
644 if (b != maxIndex) {
645 multiBytes[b] |= (byte)0x80;
646 }
647 }
648
649 return multiBytes;
650 }
651
652 private static Log _log = LogFactoryUtil.getLog(ImageToolImpl.class);
653
654 private static ImageTool _instance = new ImageToolImpl();
655
656 private static FileImpl _fileUtil = FileImpl.getInstance();
657 private static ImageMagick _imageMagick;
658
659 private Image _defaultCompanyLogo;
660 private Image _defaultOrganizationLogo;
661 private Image _defaultSpacer;
662 private Image _defaultUserFemalePortrait;
663 private Image _defaultUserMalePortrait;
664
665 private class RenderedImageFuture implements Future<RenderedImage> {
666
667 public RenderedImageFuture(
668 Future<?> future, File outputFile, String type) {
669
670 _future = future;
671 _outputFile = outputFile;
672 _type = type;
673 }
674
675 @Override
676 public boolean cancel(boolean mayInterruptIfRunning) {
677 if (_future.isCancelled() || _future.isDone()) {
678 return false;
679 }
680
681 _future.cancel(true);
682
683 return true;
684 }
685
686 @Override
687 public RenderedImage get()
688 throws ExecutionException, InterruptedException {
689
690 _future.get();
691
692 byte[] bytes = new byte[0];
693
694 try {
695 bytes = _fileUtil.getBytes(_outputFile);
696 }
697 catch (IOException ioe) {
698 throw new ExecutionException(ioe);
699 }
700
701 return read(bytes, _type);
702 }
703
704 @Override
705 public RenderedImage get(long timeout, TimeUnit timeUnit)
706 throws ExecutionException, InterruptedException, TimeoutException {
707
708 _future.get(timeout, timeUnit);
709
710 byte[] bytes = new byte[0];
711
712 try {
713 bytes = _fileUtil.getBytes(_outputFile);
714 }
715 catch (IOException ioe) {
716 throw new ExecutionException(ioe);
717 }
718
719 return read(bytes, _type);
720 }
721
722 @Override
723 public boolean isCancelled() {
724 return _future.isCancelled();
725 }
726
727 @Override
728 public boolean isDone() {
729 return _future.isDone();
730 }
731
732 private final Future<?> _future;
733 private final File _outputFile;
734 private final String _type;
735
736 }
737
738 }