Skip to content

Commit

Permalink
Refactored loadImageFromHeader to improve maintainability
Browse files Browse the repository at this point in the history
  • Loading branch information
samruddhithakor committed Nov 26, 2024
1 parent f65b599 commit 79e1b44
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 109 deletions.
208 changes: 107 additions & 101 deletions openpdf/src/main/java/com/lowagie/text/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

package com.lowagie.text;

import com.ibm.icu.impl.locale.XCldrStub.Predicate;
import com.lowagie.text.error_messages.MessageLocalization;
import com.lowagie.text.pdf.PRIndirectReference;
import com.lowagie.text.pdf.PdfArray;
Expand All @@ -72,10 +73,14 @@
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.function.BiFunction;
import java.util.ArrayList;

Check notice on line 78 in openpdf/src/main/java/com/lowagie/text/Image.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

openpdf/src/main/java/com/lowagie/text/Image.java#L78

Wrong lexicographical order for 'java.util.ArrayList' import. Should be before 'java.util.function.BiFunction'.

/**
* An <CODE>Image</CODE> is the representation of a graphic element (JPEG, PNG
* or GIF) that has to be inserted into the
* An <CODE>Image</CODE> is the representation of a graphic element
* (JPEG, PNG or GIF) that has to be inserted into the
*
* document
*
* @see Element
Expand Down Expand Up @@ -365,8 +370,9 @@ public abstract class Image extends Rectangle {

// implementation of the Element interface
/**
* Holds value of property directReference. An image is embedded into a PDF as
* an Image XObject. This object is
* Holds value of property directReference. An image is embedded into
* a PDF as an Image XObject. This object is
*
* referenced by a PdfIndirectReference object.
*/
private PdfIndirectReference directReference;
Expand Down Expand Up @@ -459,13 +465,60 @@ protected Image(Image image) {
this.transparency = image.transparency;
}

private static final List<ImageFormatHandler> IMAGE_LOADERS = new ArrayList<>();

Check notice on line 468 in openpdf/src/main/java/com/lowagie/text/Image.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

openpdf/src/main/java/com/lowagie/text/Image.java#L468

Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes.
static {

Check notice on line 469 in openpdf/src/main/java/com/lowagie/text/Image.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

openpdf/src/main/java/com/lowagie/text/Image.java#L469

'STATIC_INIT' should be separated from previous line.
IMAGE_LOADERS.add(new ImageFormatHandler(Image::isGifFormat, (rawBytes, url) ->
rawBytes != null ? ImageLoader.getGifImage(rawBytes) : ImageLoader.getGifImage(url)));
IMAGE_LOADERS.add(new ImageFormatHandler(Image::isJpegFormat, (rawBytes, url) ->
rawBytes != null ? ImageLoader.getJpegImage(rawBytes) : ImageLoader.getJpegImage(url)));
IMAGE_LOADERS.add(new ImageFormatHandler(Image::isPngFormat, (rawBytes, url) ->
rawBytes != null ? ImageLoader.getPngImage(rawBytes) : ImageLoader.getPngImage(url)));
IMAGE_LOADERS.add(new ImageFormatHandler(Image::isBmpFormat, (rawBytes, url) ->
rawBytes != null ? ImageLoader.getBmpImage(rawBytes) : ImageLoader.getBmpImage(url)));
IMAGE_LOADERS.add(new ImageFormatHandler(Image::isTiffFormat, (rawBytes, url) ->
rawBytes != null ? ImageLoader.getTiffImage(rawBytes) : ImageLoader.getTiffImage(url)));
}


private static boolean isGifFormat(int[] headerBytes) {
return headerBytes[0] == 'G' && headerBytes[1] == 'I' && headerBytes[2] == 'F';
}

private static boolean isJpegFormat(int[] headerBytes) {
return headerBytes[0] == 0xFF && headerBytes[1] == 0xD8;
}

private static boolean isPngFormat(int[] headerBytes) {
return headerBytes[0] == 137 && headerBytes[1] == 80 && headerBytes[2] == 78 && headerBytes[3] == 71;
}

private static boolean isBmpFormat(int[] headerBytes) {
return headerBytes[0] == 'B' && headerBytes[1] == 'M';
}

private static boolean isTiffFormat(int[] headerBytes) {
return (headerBytes[0] == 'I' && headerBytes[1] == 'I' && headerBytes[2] == 42 && headerBytes[3] == 0) ||
(headerBytes[0] == 'M' && headerBytes[1] == 'M' && headerBytes[2] == 0 && headerBytes[3] == 42);
}

private static class ImageFormatHandler {
Predicate<int[]> predicate;
BiFunction<byte[], URL, Image> loader;

ImageFormatHandler(Predicate<int[]> predicate, BiFunction<byte[], URL, Image> loader) {
this.predicate = predicate;
this.loader = loader;
}

}

/**
* Gets an instance of an Image.
*
* @param url an URL
* @return an Image
* @throws BadElementException if error in creating
* {@link ImgWMF#ImgWMF(byte[]) ImgWMF}
* @throws BadElementException if error in creating {@link
* ImgWMF#ImgWMF(byte[]) ImgWMF}
* @throws MalformedURLException if bad url
* @throws IOException if image is not recognized
*/
Expand All @@ -479,72 +532,25 @@ public static Image getInstance(URL url) throws BadElementException, IOException

}

// Helper Methods
private static boolean isGifFormat(int byte1, int byte2, int byte3) {
return byte1 == 'G' && byte2 == 'I' && byte3 == 'F';
}

private static boolean isJpegFormat(int byte1, int byte2) {
return byte1 == 0xFF && byte2 == 0xD8;
}

private static boolean isJpeg2000Format(int byte1, int byte2, int byte3, int byte4) {
return (byte1 == 0x00 && byte2 == 0x00 && byte3 == 0x00 && byte4 == 0x0C) ||
(byte1 == 0xFF && byte2 == 0x4F && byte3 == 0xFF && byte4 == 0x51);
}

private static boolean isPngFormat(int byte1, int byte2, int byte3, int byte4) {
return byte1 == 137 && byte2 == 80 && byte3 == 78 && byte4 == 71;
}

private static boolean isBmpFormat(int byte1, int byte2) {
return byte1 == 'B' && byte2 == 'M';
}

private static boolean isTiffFormat(int byte1, int byte2, int byte3, int byte4) {
return isBigEndianTiff(byte1, byte2, byte3, byte4) || isLittleEndianTiff(byte1, byte2, byte3, byte4);
}

private static boolean isBigEndianTiff(int byte1, int byte2, int byte3, int byte4) {
return byte1 == 'M' && byte2 == 'M' && byte3 == 0 && byte4 == 42;
}

private static boolean isLittleEndianTiff(int byte1, int byte2, int byte3, int byte4) {
return byte1 == 'I' && byte2 == 'I' && byte3 == 42 && byte4 == 0;
}

private static Image loadImageFromHeader(int[] headerBytes, byte[] rawBytes, URL url)
private static Image loadImageFromHeader(int[] headerBytes, byte[] rawBytes,
URL url)
throws IOException, BadElementException {
if (isGifFormat(headerBytes[0], headerBytes[1], headerBytes[2])) {
return (rawBytes != null) ? ImageLoader.getGifImage(rawBytes) : ImageLoader.getGifImage(url);
}
if (isJpegFormat(headerBytes[0], headerBytes[1])) {
return (rawBytes != null) ? ImageLoader.getJpegImage(rawBytes) : ImageLoader.getJpegImage(url);
}
if (isJpeg2000Format(headerBytes[0], headerBytes[1], headerBytes[2], headerBytes[3])) {
return (rawBytes != null) ? ImageLoader.getJpeg2000Image(rawBytes) : ImageLoader.getJpeg2000Image(url);
}
if (isPngFormat(headerBytes[0], headerBytes[1], headerBytes[2], headerBytes[3])) {
return (rawBytes != null) ? ImageLoader.getPngImage(rawBytes) : ImageLoader.getPngImage(url);
}
if (isBmpFormat(headerBytes[0], headerBytes[1])) {
return (rawBytes != null) ? ImageLoader.getBmpImage(rawBytes) : ImageLoader.getBmpImage(url);
}
if (isTiffFormat(headerBytes[0], headerBytes[1], headerBytes[2], headerBytes[3])) {
return (rawBytes != null) ? ImageLoader.getTiffImage(rawBytes) : ImageLoader.getTiffImage(url);
for (ImageFormatHandler handler : IMAGE_LOADERS) {
if (handler.predicate.test(headerBytes)) {
return handler.loader.apply(rawBytes, url);
}
}
throw new IOException("Unrecognized image format.");
}


/**
* Gets an instance of an Image.
*
* @param filename a filename
* @return an object of type <CODE>Gif</CODE>,<CODE>Jpeg</CODE> or
* <CODE>Png</CODE>
* @throws BadElementException if error in creating {@link ImgWMF#ImgWMF(byte[])
* ImgWMF}
* @throws BadElementException if error in creating {@link
* ImgWMF#ImgWMF(byte[]) ImgWMF}
* @throws IOException if image is not recognized
*/
public static Image getInstance(String filename)
Expand All @@ -558,8 +564,8 @@ public static Image getInstance(String filename)
* @param filename a filename
* @return an object of type <CODE>Gif</CODE>,<CODE>Jpeg</CODE> or
* <CODE>Png</CODE>
* @throws BadElementException if error in creating {@link ImgWMF#ImgWMF(byte[])
* ImgWMF}
* @throws BadElementException if error in creating {@link
* ImgWMF#ImgWMF(byte[]) ImgWMF}
* @throws IOException if image is not recognized
*/
public static Image getInstanceFromClasspath(String filename)
Expand All @@ -573,8 +579,8 @@ public static Image getInstanceFromClasspath(String filename)
*
* @param imgb raw image date
* @return an Image object
* @throws BadElementException if error in creating {@link ImgWMF#ImgWMF(byte[])
* ImgWMF}
* @throws BadElementException if error in creating {@link
* ImgWMF#ImgWMF(byte[]) ImgWMF}
* @throws IOException if image is not recognized
*/
public static Image getInstance(byte[] imgb) throws BadElementException,
Expand Down Expand Up @@ -650,17 +656,17 @@ public static Image getInstance(int width, int height, boolean reverseBits,
}

/**
* Creates an Image with CCITT G3 or G4 compression. It assumes that the data
* bytes are already compressed.
* Creates an Image with CCITT G3 or G4 compression. It assumes that
* the data bytes are already compressed.
*
* @param width the exact width of the image
* @param height the exact height of the image
* @param reverseBits reverses the bits in <code>data</code>. Bit 0 is swapped
* with bit 7 and so on
* @param typeCCITT the type of compression in <code>data</code>. It can be
* CCITTG4, CCITTG31D, CCITTG32D
* @param parameters parameters associated with this stream. Possible values
* are CCITT_BLACKIS1,
* @param reverseBits reverses the bits in <code>data</code>. Bit 0
* is swapped with bit 7 and so on
* @param typeCCITT the type of compression in <code>data</code>.
* It can be CCITTG4, CCITTG31D, CCITTG32D
* @param parameters parameters associated with this stream.
* Possible values are CCITT_BLACKIS1,
* CCITT_ENCODEDBYTEALIGN, CCITT_ENDOFLINE and
* CCITT_ENDOFBLOCK or a combination of them
* @param data the image data
Expand Down Expand Up @@ -690,8 +696,8 @@ public static Image getInstance(int width, int height, boolean reverseBits,
* @param components 1,3 or 4 for GrayScale, RGB and CMYK
* @param data the image data
* @param bpc bits per component
* @param transparency transparency information in the Mask format of the image
* dictionary
* @param transparency transparency information in the Mask format of
* the image dictionary
* @return an object of type <CODE>ImgRaw</CODE>
* @throws BadElementException on error
*/
Expand All @@ -715,8 +721,8 @@ public static Image getInstance(int width, int height, int components,
/**
* gets an instance of an Image
*
* @param template a PdfTemplate that has to be wrapped in an <code>Image</code>
* object
* @param template a PdfTemplate that has to be wrapped in an
* <code>Image</code> object
* @return an Image object
* @throws BadElementException on error
*/
Expand All @@ -729,8 +735,8 @@ public static Image getInstance(PdfTemplate template)
* Gets an instance of an Image from a java.awt.Image.
*
* @param image the <CODE>java.awt.Image</CODE> to convert
* @param color if different from <CODE>null</CODE> the transparency pixels
* are replaced by this color
* @param color if different from <CODE>null</CODE> the
* transparency pixels are replaced by this color
* @param forceBW if <CODE>true</CODE> the image is treated as black and white
* @return an object of type <CODE>ImgRaw</CODE>
* @throws BadElementException on error
Expand Down Expand Up @@ -921,11 +927,11 @@ public static Image getInstance(java.awt.Image image, java.awt.Color color)
}

/**
* Gets an instance of a Image from a java.awt.Image. The image is added as a
* JPEG with a user defined quality.
* Gets an instance of a Image from a java.awt.Image. The image is
* added as a JPEG with a user defined quality.
*
* @param writer the <CODE>PdfWriter</CODE> object to which the image will be
* added
* @param writer the <CODE>PdfWriter</CODE> object to which the
* image will be added
* @param awtImage the <CODE>java.awt.Image</CODE> to convert
* @param quality a float value between <code>0</code> and <code>1</code>
* @return an object of type <CODE>PdfTemplate</CODE>
Expand All @@ -940,11 +946,11 @@ public static Image getInstance(PdfWriter writer, java.awt.Image awtImage, float
// width and height

/**
* Gets an instance of a Image from a java.awt.Image. The image is added as a
* JPEG with a user defined quality.
* Gets an instance of a Image from a java.awt.Image. The image is
* added as a JPEG with a user defined quality.
*
* @param cb the <CODE>PdfContentByte</CODE> object to which the image
* will be added
* @param cb the <CODE>PdfContentByte</CODE> object to which
* the image will be added
* @param awtImage the <CODE>java.awt.Image</CODE> to convert
* @param quality a float value between <code>0</code> and <code>1</code>
* @return an object of type <CODE>PdfTemplate</CODE>
Expand Down Expand Up @@ -1089,8 +1095,8 @@ public boolean isImgRaw() {
}

/**
* Returns <CODE>true</CODE> if the image is an <CODE>ImgTemplate</CODE>
* -object.
* Returns <CODE>true</CODE> if the image is an
* <CODE>ImgTemplate</CODE> -object.
*
* @return a <CODE>boolean</CODE>
*/
Expand Down Expand Up @@ -1475,8 +1481,8 @@ public float getInitialRotation() {
// interpolation

/**
* Some image formats, like TIFF may present the images rotated that have to be
* compensated.
* Some image formats, like TIFF may present the images rotated that
* have to be compensated.
*
* @param initialRotation New value of property initialRotation.
*/
Expand Down Expand Up @@ -1605,8 +1611,8 @@ public void setAnnotation(Annotation annotation) {
/**
* Gets the layer this image belongs to.
*
* @return the layer this image belongs to or <code>null</code> for no layer
* defined
* @return the layer this image belongs to or <code>null</code> for
* no layer defined
*/
public PdfOCG getLayer() {
return layer;
Expand All @@ -1631,8 +1637,8 @@ public boolean isInterpolation() {
}

/**
* Sets the image interpolation. Image interpolation attempts to produce a
* smooth transition between adjacent sample
* Sets the image interpolation. Image interpolation attempts to
* produce a smooth transition between adjacent sample
* values.
*
* @param interpolation New value of property interpolation.
Expand Down Expand Up @@ -1889,8 +1895,8 @@ public void makeMask() throws DocumentException {
}

/**
* Returns <CODE>true</CODE> if this <CODE>Image</CODE> has the requisites to be
* a mask.
* Returns <CODE>true</CODE> if this <CODE>Image</CODE> has the
* requisites to be a mask.
*
* @return <CODE>true</CODE> if this <CODE>Image</CODE> can be a mask
*/
Expand Down Expand Up @@ -1971,17 +1977,17 @@ public void setTransparency(int[] transparency) {
/**
* Returns the compression level used for images written as a compressed stream.
*
* @return the compression level (0 = best speed, 9 = best compression, -1 is
* default)
* @return the compression level (0 = best speed, 9 = best
* compression, -1 is default)
* @since 2.1.3
*/
public int getCompressionLevel() {
return compressionLevel;
}

/**
* Sets the compression level to be used if the image is written as a compressed
* stream.
* Sets the compression level to be used if the image is written as a
* compressed stream.
*
* @param compressionLevel a value between 0 (best speed) and 9 (best
* compression)
Expand Down
10 changes: 2 additions & 8 deletions openpdf/src/test/java/com/lowagie/text/ImageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

class ImageTest {

// For performance testing, set this to something > 100
private static final int PERFORMANCE_ITERATIONS = 1;

@Test
Expand Down Expand Up @@ -53,12 +54,5 @@ void shouldReturnImageWithUrlForPath() throws Exception {
final Image image = Image.getInstance(ClassLoader.getSystemResource(fileName));
assertNotNull(image.getUrl(), "Image URL should not be null when loaded from a file path");
}

private byte[] readFileBytes(String filePath) throws IOException {
InputStream stream = ClassLoader.getSystemResourceAsStream(filePath);
if (stream == null) {
throw new IOException("File not found: " + filePath);
}
return stream.readAllBytes();
}

}

0 comments on commit 79e1b44

Please sign in to comment.