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