- Keep aspect ratio of images rendered directly by browser such as gif

and svg.
- Corrected quadratic rendering of landscape images with height smaller
than maxHeight
pull/21/head
luc 9 years ago
parent e2d00585e2
commit 37e28e0dd3

@ -106,7 +106,6 @@ public class ViewImage {
return null; return null;
} }
// get the image as stream // get the image as stream
if (MemoryControl.shortStatus()) { if (MemoryControl.shortStatus()) {
iconcache.clear(); iconcache.clear();
@ -131,7 +130,9 @@ public class ViewImage {
if (resourceb == null) { if (resourceb == null) {
if (urlString.endsWith(".ico")) { if (urlString.endsWith(".ico")) {
// load default favicon dfltfvcn.ico // load default favicon dfltfvcn.ico
// Should not do this here : we can be displaying search image result of '.ico' type and do not want to display a default // Should not do this here : we can be displaying search
// image result of '.ico' type and do not want to display a
// default
if (defaulticonb == null) if (defaulticonb == null)
try { try {
resourceb = FileUtils.read(new File(sb.getAppPath(), defaulticon)); resourceb = FileUtils.read(new File(sb.getAppPath(), defaulticon));
@ -148,13 +149,8 @@ public class ViewImage {
} }
} }
// gif images are not loaded because of an animated gif bug within String urlExt = MultiProtocolURL.getFileExtension(url.getFileName());
// jvm which sends java into an endless loop with high CPU if (ext != null && ext.equalsIgnoreCase(urlExt) && isBrowserRendered(urlExt)) {
if (ext.equals("gif") && "gif".equals(MultiProtocolURL.getFileExtension(url.getFileName()))) {
return new ByteArrayInputStream(resourceb);
} else if (ext.equals("svg") && "svg".equals(MultiProtocolURL.getFileExtension(url.getFileName()))) {
// svg images not supported by awt, but by most browser, deliver
// just content (without crop/scale)
return new ByteArrayInputStream(resourceb); return new ByteArrayInputStream(resourceb);
} }
@ -166,32 +162,57 @@ public class ViewImage {
} }
/** /**
* Process resourceb byte array to try to produce an Image instance eventually scaled and cropped depending on post parameters * @param formatName
* @param post request post parameters. Must not be null. * informal file format name. For example : "png".
* @param auth true when access rigths are OK. * @return true when image format is rendered by browser and not by
* @param urlString image source URL. Must not be null. * ViewImage internals
* @param ext image file extension. May be null. */
* @param okToCache true when image can be cached public static boolean isBrowserRendered(String formatName) {
* @param resourceb byte array. Must not be null. /*
* gif images are not loaded because of an animated gif bug within jvm
* which sends java into an endless loop with high CPU
*/
/*
* svg images not supported by jdk, but by most browser, deliver just
* content (without crop/scale)
*/
return ("gif".equalsIgnoreCase(formatName) || "svg".equalsIgnoreCase(formatName));
}
/**
* Process resourceb byte array to try to produce an Image instance
* eventually scaled and cropped depending on post parameters
*
* @param post
* request post parameters. Must not be null.
* @param auth
* true when access rigths are OK.
* @param urlString
* image source URL. Must not be null.
* @param ext
* image file extension. May be null.
* @param okToCache
* true when image can be cached
* @param resourceb
* byte array. Must not be null.
* @return an Image instance when parsing is OK, or null. * @return an Image instance when parsing is OK, or null.
*/ */
protected static EncodedImage parseAndScale(serverObjects post, boolean auth, String urlString, String ext, boolean okToCache, byte[] resourceb) { protected static EncodedImage parseAndScale(serverObjects post, boolean auth, String urlString, String ext,
boolean okToCache, byte[] resourceb) {
EncodedImage encodedImage = null; EncodedImage encodedImage = null;
Image image = ImageParser.parse(urlString, resourceb); Image image = ImageParser.parse(urlString, resourceb);
if (image != null) { if (image != null) {
int width = post.getInt("width", 0);
int height = post.getInt("height", 0);
int maxwidth = post.getInt("maxwidth", 0); int maxwidth = post.getInt("maxwidth", 0);
int maxheight = post.getInt("maxheight", 0); int maxheight = post.getInt("maxheight", 0);
final boolean quadratic = post.containsKey("quadratic"); final boolean quadratic = post.containsKey("quadratic");
boolean isStatic = post.getBoolean("isStatic"); boolean isStatic = post.getBoolean("isStatic");
if (!auth || (width != 0 && height != 0) || maxwidth != 0 || maxheight != 0) { if (!auth || maxwidth != 0 || maxheight != 0) {
// find original size // find original size
final int h = image.getHeight(null); int h = image.getHeight(null);
final int w = image.getWidth(null); int w = image.getWidth(null);
// in case of not-authorized access shrink the image to // in case of not-authorized access shrink the image to
// prevent // prevent
@ -203,9 +224,11 @@ public class ViewImage {
// quadratic shape // quadratic shape
if (quadratic && w != h) { if (quadratic && w != h) {
image = makeSquare(image, h, w); image = makeSquare(image, h, w);
h = image.getHeight(null);
w = image.getWidth(null);
} }
Dimension finalDimensions = calculateDimensions(w, h, width, height, maxDimensions); Dimension finalDimensions = calculateDimensions(w, h, maxDimensions);
if (w != finalDimensions.width && h != finalDimensions.height) { if (w != finalDimensions.width && h != finalDimensions.height) {
image = scale(finalDimensions.width, finalDimensions.height, image); image = scale(finalDimensions.width, finalDimensions.height, image);
@ -230,10 +253,9 @@ public class ViewImage {
* *
* @return dimensions to render image * @return dimensions to render image
*/ */
protected static Dimension calculateDimensions(final int originWidth, final int originHeight, final int targetWidth, protected static Dimension calculateDimensions(final int originWidth, final int originHeight, final Dimension max) {
final int targetHeight, final Dimension max) { int resultWidth;
int resultWidth = targetWidth; int resultHeight;
int resultHeight = targetHeight;
if (max.width < originWidth || max.height < originHeight) { if (max.width < originWidth || max.height < originHeight) {
// scale image // scale image
final double hs = (originWidth <= max.width) ? 1.0 : ((double) max.width) / ((double) originWidth); final double hs = (originWidth <= max.width) ? 1.0 : ((double) max.width) / ((double) originWidth);
@ -335,13 +357,15 @@ public class ViewImage {
if (w > h) { if (w > h) {
final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_RGB); final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = dst.createGraphics(); Graphics2D g = dst.createGraphics();
g.drawImage(image, 0, 0, h - 1, h - 1, (w - h) / 2, 0, h + (w - h) / 2, h - 1, null); final int offset = (w - h) / 2;
g.drawImage(image, 0, 0, h - 1, h - 1, offset, 0, h + offset, h - 1, null);
g.dispose(); g.dispose();
image = dst; image = dst;
} else { } else {
final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_RGB); final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_RGB);
Graphics2D g = dst.createGraphics(); Graphics2D g = dst.createGraphics();
g.drawImage(image, 0, 0, w - 1, w - 1, 0, (h - w) / 2, w - 1, w + (h - w) / 2, null); final int offset = (h - w) / 2;
g.drawImage(image, 0, 0, w - 1, w - 1, 0, offset, w - 1, w + offset, null);
g.dispose(); g.dispose();
image = dst; image = dst;
} }

@ -260,6 +260,8 @@ tt, *.tt {
width: 128px; width: 128px;
height: 128px; /* 96px thumbnail + some lines of text */ height: 128px; /* 96px thumbnail + some lines of text */
float: left; float: left;
/* Cut non square images not rendered by YaCy ViewImage */
overflow: hidden;
} }
.hides .hoverShow { .hides .hoverShow {

@ -41,7 +41,7 @@
:: ::
#(item)#::<div class="thumbcontainer"> #(item)#::<div class="thumbcontainer">
<a href="#[hrefFullPreview]#" target="#[target]#" class="thumblink" onclick="return hs.expand(this)"> <a href="#[hrefFullPreview]#" target="#[target]#" class="thumblink" onclick="return hs.expand(this)">
<img src="#[hrefCache]#" width="128" height="128" alt="#[name]#" /> <img src="#[hrefCache]#" width="#[width]#" height="#[height]#" style="#[style]#" alt="#[name]#" />
</a> </a>
<div class="highslide-caption"><a href="#[href]#" target="#[target]#">#[name]#</a><br /><a href="#[source]#" target="#[target]#">#[sourcedom]#</a></div> <div class="highslide-caption"><a href="#[href]#" target="#[target]#">#[name]#</a><br /><a href="#[source]#" target="#[target]#">#[sourcedom]#</a></div>
</div>#(/item)# </div>#(/item)#

@ -73,6 +73,10 @@ public class yacysearchitem {
private static final int SHORTEN_SUFFIX_LENGTH = SHORTEN_SUFFIX.length(); private static final int SHORTEN_SUFFIX_LENGTH = SHORTEN_SUFFIX.length();
private static final int MAX_NAME_LENGTH = 60; private static final int MAX_NAME_LENGTH = 60;
private static final int MAX_URL_LENGTH = 120; private static final int MAX_URL_LENGTH = 120;
/** Default image item width in pixels */
private static final int DEFAULT_IMG_WIDTH = 128;
/** Default image item height in pixels */
private static final int DEFAULT_IMG_HEIGHT = DEFAULT_IMG_WIDTH;
//private static boolean col = true; //private static boolean col = true;
@ -81,7 +85,7 @@ public class yacysearchitem {
final serverObjects prop = new serverObjects(); final serverObjects prop = new serverObjects();
final String eventID = post.get("eventID", ""); final String eventID = post.get("eventID", "");
final boolean authenticated = sb.adminAuthenticated(header) >= 2; final boolean authenticated = sb.verifyAuthentication(header);
final int item = post.getInt("item", -1); final int item = post.getInt("item", -1);
final RequestHeader.FileType fileType = header.fileType(); final RequestHeader.FileType fileType = header.fileType();
@ -306,39 +310,7 @@ public class yacysearchitem {
if (theSearch.query.contentdom == Classification.ContentDomain.IMAGE) { if (theSearch.query.contentdom == Classification.ContentDomain.IMAGE) {
// image search; shows thumbnails // image search; shows thumbnails
processImage(sb, prop, item, theSearch, target_special_pattern, timeout);
prop.put("content", theSearch.query.contentdom.getCode() + 1); // switch on specific content
try {
SearchEvent.ImageResult image = theSearch.oneImageResult(item, timeout);
final String imageUrlstring = image.imageUrl.toNormalform(true);
final String imageUrlExt = MultiProtocolURL.getFileExtension(image.imageUrl.getFileName());
final String target = sb.getConfig(imageUrlstring.matches(target_special_pattern) ? SwitchboardConstants.SEARCH_TARGET_SPECIAL : SwitchboardConstants.SEARCH_TARGET_DEFAULT, "_self");
final String license = URLLicense.aquireLicense(image.imageUrl); // this is just the license key to get the image forwarded through the YaCy thumbnail viewer, not an actual lawful license
/* Image format ouput for ViewImage servlet : default is png, except with gif and svg images */
final String viewImageExt = !imageUrlExt.isEmpty() && "gif.png.svg".contains(imageUrlExt) ? imageUrlExt : "png";
/* Thumb URL */
prop.putHTML("content_item_hrefCache", "ViewImage." + viewImageExt + "?maxwidth=128&maxheight=128&code="+license+"&isStatic=true&quadratic=&url=" + imageUrlstring);
/* Full size preview URL */
prop.putHTML("content_item_hrefFullPreview", "ViewImage." + viewImageExt + "?code="+license+"&isStatic=true&url=" + imageUrlstring);
prop.putHTML("content_item_href", imageUrlstring);
prop.putHTML("content_item_target", target);
prop.put("content_item_code", license);
prop.putHTML("content_item_name", shorten(image.imagetext, MAX_NAME_LENGTH));
prop.put("content_item_mimetype", image.mimetype);
prop.put("content_item_fileSize", 0);
prop.put("content_item_width", image.width);
prop.put("content_item_height", image.height);
prop.put("content_item_attr", ""/*(ms.attr.equals("-1 x -1")) ? "" : "(" + ms.attr + ")"*/); // attributes, here: original size of image
prop.put("content_item_urlhash", ASCII.String(image.imageUrl.hash()));
prop.put("content_item_source", image.sourceUrl.toNormalform(true));
prop.putXML("content_item_source-xml", image.sourceUrl.toNormalform(true));
prop.put("content_item_sourcedom", image.sourceUrl.getHost());
prop.put("content_item_nl", (item == theSearch.query.offset) ? 0 : 1);
prop.put("content_item", 1);
} catch (MalformedURLException e) {
prop.put("content_item", "0");
}
theSearch.query.transmitcount = item + 1; theSearch.query.transmitcount = item + 1;
return prop; return prop;
} }
@ -371,6 +343,79 @@ public class yacysearchitem {
return prop; return prop;
} }
/**
* Process search of image type and feed prop object. All parameters must not be null.
* @param sb Switchboard instance
* @param prop result
* @param item item index.
* @param theSearch search event
* @param target_special_pattern
* @param timeout result getting timeOut
*/
private static void processImage(final Switchboard sb, final serverObjects prop, final int item,
final SearchEvent theSearch, final String target_special_pattern, long timeout) {
prop.put("content", theSearch.query.contentdom.getCode() + 1); // switch on specific content
try {
SearchEvent.ImageResult image = theSearch.oneImageResult(item, timeout);
final String imageUrlstring = image.imageUrl.toNormalform(true);
final String imageUrlExt = MultiProtocolURL.getFileExtension(image.imageUrl.getFileName());
final String target = sb.getConfig(imageUrlstring.matches(target_special_pattern) ? SwitchboardConstants.SEARCH_TARGET_SPECIAL : SwitchboardConstants.SEARCH_TARGET_DEFAULT, "_self");
final String license = URLLicense.aquireLicense(image.imageUrl); // this is just the license key to get the image forwarded through the YaCy thumbnail viewer, not an actual lawful license
/* Image format ouput for ViewImage servlet : default is png, except with gif and svg images */
final String viewImageExt = !imageUrlExt.isEmpty() && ViewImage.isBrowserRendered(imageUrlExt) ? imageUrlExt : "png";
/* Thumb URL */
prop.putHTML("content_item_hrefCache", "ViewImage." + viewImageExt + "?maxwidth=" + DEFAULT_IMG_WIDTH + "&maxheight=" + DEFAULT_IMG_HEIGHT + "&code="+license+"&isStatic=true&quadratic=&url=" + imageUrlstring);
/* Full size preview URL */
prop.putHTML("content_item_hrefFullPreview", "ViewImage." + viewImageExt + "?code="+license+"&isStatic=true&url=" + imageUrlstring);
prop.putHTML("content_item_href", imageUrlstring);
prop.putHTML("content_item_target", target);
prop.put("content_item_code", license);
prop.putHTML("content_item_name", shorten(image.imagetext, MAX_NAME_LENGTH));
prop.put("content_item_mimetype", image.mimetype);
prop.put("content_item_fileSize", 0);
String itemWidth = DEFAULT_IMG_WIDTH + "px", itemHeight = DEFAULT_IMG_HEIGHT + "px", itemStyle="";
/* When image content is rendered by browser :
* - set smaller dimension to 100% in order to crop image on other dimension with CSS style 'overflow:hidden' on image container
* - set negative margin top behave like ViewImage which sets an offset when cutting to square */
if (ViewImage.isBrowserRendered(imageUrlExt)) {
if (image.width > image.height) {
/* Landscape orientation */
itemWidth = "";
itemHeight = "100%";
if(image.height > 0) {
double scale = ((double)DEFAULT_IMG_HEIGHT) / ((double)image.height);
int margin = (int)((image.height - image.width) * (scale / 2.0));
itemStyle = "margin-left: " + margin + "px;";
}
} else {
/* Portrait orientation, or square or unknown dimensions (both equals zero) */
itemWidth = "100%";
itemHeight = "";
if(image.height > image.width && image.width > 0) {
double scale = ((double)DEFAULT_IMG_WIDTH) / ((double)image.width);
int margin = (int)((image.width - image.height) * (scale / 2.0));
itemStyle = "margin-top: " + margin + "px;";
}
}
}
prop.put("content_item_width", itemWidth);
prop.put("content_item_height", itemHeight);
prop.put("content_item_style", itemStyle);
prop.put("content_item_attr", ""/*(ms.attr.equals("-1 x -1")) ? "" : "(" + ms.attr + ")"*/); // attributes, here: original size of image
prop.put("content_item_urlhash", ASCII.String(image.imageUrl.hash()));
prop.put("content_item_source", image.sourceUrl.toNormalform(true));
prop.putXML("content_item_source-xml", image.sourceUrl.toNormalform(true));
prop.put("content_item_sourcedom", image.sourceUrl.getHost());
prop.put("content_item_nl", (item == theSearch.query.offset) ? 0 : 1);
prop.put("content_item", 1);
} catch (MalformedURLException e) {
prop.put("content_item", "0");
}
}
private static String shorten(final String s, final int length) { private static String shorten(final String s, final int length) {
final String ret; final String ret;
if (s.length() <= length) { if (s.length() <= length) {

Loading…
Cancel
Save