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