diff --git a/htroot/ViewImage.java b/htroot/ViewImage.java index b967b720e..bccbb1642 100644 --- a/htroot/ViewImage.java +++ b/htroot/ViewImage.java @@ -106,7 +106,6 @@ public class ViewImage { return null; } - // get the image as stream if (MemoryControl.shortStatus()) { iconcache.clear(); @@ -131,7 +130,9 @@ public class ViewImage { if (resourceb == null) { if (urlString.endsWith(".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) try { resourceb = FileUtils.read(new File(sb.getAppPath(), defaulticon)); @@ -148,50 +149,70 @@ public class ViewImage { } } - // gif images are not loaded because of an animated gif bug within - // jvm which sends java into an endless loop with high CPU - 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) + String urlExt = MultiProtocolURL.getFileExtension(url.getFileName()); + if (ext != null && ext.equalsIgnoreCase(urlExt) && isBrowserRendered(urlExt)) { return new ByteArrayInputStream(resourceb); } // read image - encodedImage = parseAndScale(post, auth, urlString, ext, okToCache, resourceb); + encodedImage = parseAndScale(post, auth, urlString, ext, okToCache, resourceb); } return encodedImage; } /** - * 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. + * @param formatName + * informal file format name. For example : "png". + * @return true when image format is rendered by browser and not by + * ViewImage internals + */ + public static boolean isBrowserRendered(String formatName) { + /* + * 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. */ - 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; - + Image image = ImageParser.parse(urlString, resourceb); if (image != null) { - int width = post.getInt("width", 0); - int height = post.getInt("height", 0); int maxwidth = post.getInt("maxwidth", 0); int maxheight = post.getInt("maxheight", 0); final boolean quadratic = post.containsKey("quadratic"); boolean isStatic = post.getBoolean("isStatic"); - if (!auth || (width != 0 && height != 0) || maxwidth != 0 || maxheight != 0) { + if (!auth || maxwidth != 0 || maxheight != 0) { // find original size - final int h = image.getHeight(null); - final int w = image.getWidth(null); + int h = image.getHeight(null); + int w = image.getWidth(null); // in case of not-authorized access shrink the image to // prevent @@ -203,9 +224,11 @@ public class ViewImage { // quadratic shape if (quadratic && w != h) { 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) { image = scale(finalDimensions.width, finalDimensions.height, image); @@ -230,10 +253,9 @@ public class ViewImage { * * @return dimensions to render image */ - protected static Dimension calculateDimensions(final int originWidth, final int originHeight, final int targetWidth, - final int targetHeight, final Dimension max) { - int resultWidth = targetWidth; - int resultHeight = targetHeight; + protected static Dimension calculateDimensions(final int originWidth, final int originHeight, final Dimension max) { + int resultWidth; + int resultHeight; if (max.width < originWidth || max.height < originHeight) { // scale image final double hs = (originWidth <= max.width) ? 1.0 : ((double) max.width) / ((double) originWidth); @@ -321,7 +343,7 @@ public class ViewImage { } return image; } - + /** * Crop image to make a square * @@ -335,13 +357,15 @@ public class ViewImage { if (w > h) { final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_RGB); 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(); image = dst; } else { final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_RGB); 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(); image = dst; } diff --git a/htroot/env/base.css b/htroot/env/base.css index 6f908e89b..c65877797 100644 --- a/htroot/env/base.css +++ b/htroot/env/base.css @@ -260,6 +260,8 @@ tt, *.tt { width: 128px; height: 128px; /* 96px thumbnail + some lines of text */ float: left; + /* Cut non square images not rendered by YaCy ViewImage */ + overflow: hidden; } .hides .hoverShow { diff --git a/htroot/yacysearchitem.html b/htroot/yacysearchitem.html index 416b63044..6fc9c07d8 100644 --- a/htroot/yacysearchitem.html +++ b/htroot/yacysearchitem.html @@ -41,7 +41,7 @@ :: #(item)#::
#(/item)# diff --git a/htroot/yacysearchitem.java b/htroot/yacysearchitem.java index 492e4e314..b6c2c41dd 100644 --- a/htroot/yacysearchitem.java +++ b/htroot/yacysearchitem.java @@ -73,6 +73,10 @@ public class yacysearchitem { private static final int SHORTEN_SUFFIX_LENGTH = SHORTEN_SUFFIX.length(); private static final int MAX_NAME_LENGTH = 60; 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; @@ -81,7 +85,7 @@ public class yacysearchitem { final serverObjects prop = new serverObjects(); 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 RequestHeader.FileType fileType = header.fileType(); @@ -306,39 +310,7 @@ public class yacysearchitem { if (theSearch.query.contentdom == Classification.ContentDomain.IMAGE) { // image search; shows thumbnails - - 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"); - } + processImage(sb, prop, item, theSearch, target_special_pattern, timeout); theSearch.query.transmitcount = item + 1; return prop; } @@ -370,6 +342,79 @@ public class yacysearchitem { 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) { final String ret;