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