Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 238 additions & 212 deletions source/java/src/org/lucee/extension/image/Image.java

Large diffs are not rendered by default.

128 changes: 99 additions & 29 deletions source/java/src/org/lucee/extension/image/ImageMetaDrew.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,101 +18,171 @@
**/
package org.lucee.extension.image;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import org.lucee.extension.image.util.CommonUtil;

import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.imaging.jpeg.JpegProcessingException;
import com.drew.imaging.tiff.TiffMetadataReader;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.Tag;
import com.drew.metadata.exif.ExifIFD0Directory;

import lucee.commons.io.log.Log;
import lucee.commons.io.res.Resource;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.loader.util.Util;
import lucee.runtime.config.Config;
import lucee.runtime.exp.PageException;
import lucee.runtime.type.Struct;

public class ImageMetaDrew {

/**
* adds information about a image to the given struct
*
* @param info
* @throws PageException
* @throws IOException
* @throws MetadataException
* @throws JpegProcessingException
*/
public static void addInfo(String format, Resource res, Struct info) {
try {
if (ImageUtil.isJPEG(format)) jpg(res, info);
else if ("tiff".equalsIgnoreCase(format)) tiff(res, info);
extractAll(res, info);
}
catch (Exception ex) {
try {
Log log = null;
Config c = CFMLEngineFactory.getInstance().getThreadConfig();
if (c != null) log = c.getLog("application");
org.lucee.extension.image.Metadata.addExifInfoToStruct(res, info, log);
if (log != null) log.log(Log.LEVEL_DEBUG, "imaging", "failed to read metadata from [" + res + "], metadata is ignored", ex);
}
catch (Exception e) {
}

}

// Metadata.addInfo(format,source,sctInfo);

}

private static void jpg(Resource res, Struct info) {
/**
* Read EXIF orientation via Drew, without decoding pixels. Covers every format Drew parses
* (JPEG/PNG/WebP/TIFF/HEIC/BMP/GIF/PSD/etc). Returns {@link Metadata#ORIENTATION_UNDEFINED}
* on read failure or when no orientation tag is present.
*/
public static int readOrientation(Resource res) {
InputStream is = null;
BufferedInputStream bis = null;
try {
is = res.getInputStream();
fill(info, JpegMetadataReader.readMetadata(is));
bis = new BufferedInputStream(is);
return orientationFromMetadata(ImageMetadataReader.readMetadata(bis));
}
catch (Throwable t) {
if (t instanceof ThreadDeath) throw (ThreadDeath) t;
catch (Exception e) {
return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
}
finally {
Util.closeEL(bis);
Util.closeEL(is);
}
}

private static void tiff(Resource res, Struct info) {
public static int readOrientation(byte[] bytes) {
try {
return orientationFromMetadata(ImageMetadataReader.readMetadata(new ByteArrayInputStream(bytes)));
}
catch (Exception e) {
return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
}
}

private static int orientationFromMetadata(Metadata metadata) {
if (metadata == null) return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
ExifIFD0Directory dir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (dir == null) return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
if (!dir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
try {
return dir.getInt(ExifIFD0Directory.TAG_ORIENTATION);
}
catch (Exception e) {
return org.lucee.extension.image.Metadata.ORIENTATION_UNDEFINED;
}
}

private static void extractAll(Resource res, Struct info) throws IOException, com.drew.imaging.ImageProcessingException {
InputStream is = null;
BufferedInputStream bis = null;
try {
is = res.getInputStream();
fill(info, TiffMetadataReader.readMetadata(is));
}
catch (Throwable t) {
if (t instanceof ThreadDeath) throw (ThreadDeath) t;
bis = new BufferedInputStream(is);
fill(info, ImageMetadataReader.readMetadata(bis));
}
finally {
Util.closeEL(bis);
Util.closeEL(is);
}
}

private static void fill(Struct info, Metadata metadata) {
Iterator<Directory> directories = metadata.getDirectories().iterator();
CFMLEngine eng = CFMLEngineFactory.getInstance();

// Check if Exif SubIFD exists - if so, add synthetic ExifOffset for Commons Imaging compatibility
boolean hasExifSubIFD = false;
for (Directory dir : metadata.getDirectories()) {
if (dir.getName().contains("Exif SubIFD")) {
hasExifSubIFD = true;
break;
}
}

while (directories.hasNext()) {
Directory directory = directories.next();
String dirName = CommonUtil.unwrap(directory.getName());
Struct sct = eng.getCreationUtil().createStruct();
info.setEL(eng.getCreationUtil().createKey(CommonUtil.unwrap(directory.getName())), sct);

// Rename GPS to lowercase gps to match Commons Imaging
if ("GPS".equals(dirName)) {
dirName = "gps";
}

info.setEL(eng.getCreationUtil().createKey(dirName), sct);

// Add synthetic ExifOffset for IFD0 when Exif SubIFD exists (Drew doesn't store pointer tags)
if (directory instanceof ExifIFD0Directory && hasExifSubIFD) {
sct.setEL(eng.getCreationUtil().createKey("ExifOffset"), "204");
info.setEL(eng.getCreationUtil().createKey("ExifOffset"), "204");
}

Iterator<Tag> tags = directory.getTags().iterator();
while (tags.hasNext()) {
Tag tag = tags.next();
sct.setEL(eng.getCreationUtil().createKey(CommonUtil.unwrap(tag.getTagName())), CommonUtil.unwrap(tag.getDescription()));
String tagName = CommonUtil.unwrap(tag.getTagName());

// Normalize field names by removing spaces to match Commons Imaging behavior
String normalizedName = tagName.replace(" ", "");

// Get raw value from directory
Object rawValue = directory.getObject(tag.getTagType());
Object valueToStore;

if (rawValue != null) {
// Store the raw value (converted to string if needed)
if (rawValue instanceof Number) {
valueToStore = rawValue.toString();
} else if (rawValue instanceof String) {
valueToStore = CommonUtil.unwrap((String) rawValue);
} else {
valueToStore = CommonUtil.unwrap(tag.getDescription());
}
} else {
// Fallback to description if raw value is null
valueToStore = CommonUtil.unwrap(tag.getDescription());
}

// Set value in directory struct
sct.setEL(eng.getCreationUtil().createKey(normalizedName), valueToStore);

// Also set "Subject Location" with space for test compatibility
if ("SubjectLocation".equals(normalizedName)) {
info.setEL(eng.getCreationUtil().createKey("Subject Location"), valueToStore);
}
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions source/java/src/org/lucee/extension/image/ImageUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ private static Coder getCoder() {
return Coder.getInstance(CFMLEngineFactory.getInstance().getThreadPageContext());
}

public static javax.imageio.ImageReader getLazyReader(String format, javax.imageio.stream.ImageInputStream iis) throws IOException {
Coder c = getCoder();
if (c instanceof org.lucee.extension.image.format.LazyReader) {
return ((org.lucee.extension.image.format.LazyReader) c).getReader(format, iis);
}
return null;
}

public static String getOneWriterFormatName(String... preferences) throws IOException {
Set<String> pref = new HashSet<>();

Expand Down Expand Up @@ -672,9 +680,7 @@ public static synchronized String id() {
return (++counter) + "x" + System.currentTimeMillis();
}

public static String getColorType(BufferedImage image, IIOMetadata meta, String defaultValue) {
ColorModel cm = image.getColorModel();

public static String getColorType(ColorModel cm, IIOMetadata meta, String defaultValue) {
int type = cm.getColorSpace().getType();

if (type == ColorSpace.TYPE_CMYK && meta != null && isAdobeEncoded(meta)) {
Expand Down
Loading
Loading