diff --git a/htroot/ViewFavicon.java b/htroot/ViewFavicon.java new file mode 100644 index 000000000..68c45125b --- /dev/null +++ b/htroot/ViewFavicon.java @@ -0,0 +1,198 @@ + +// ViewFavicon.java +// ----------------------- +// part of YaCy +// (C) by Michael Peter Christen; mc@yacy.net +// first published on http://www.anomic.de +// Frankfurt, Germany, 2006 +// created 03.04.2006 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; + +import net.yacy.cora.document.id.DigestURL; +import net.yacy.cora.document.id.MultiProtocolURL; +import net.yacy.cora.protocol.Domains; +import net.yacy.cora.protocol.HeaderFramework; +import net.yacy.cora.protocol.RequestHeader; +import net.yacy.cora.storage.ConcurrentARC; +import net.yacy.cora.util.ConcurrentLog; +import net.yacy.kelondro.util.FileUtils; +import net.yacy.kelondro.util.MemoryControl; +import net.yacy.kelondro.workflow.WorkflowProcessor; +import net.yacy.peers.graphics.EncodedImage; +import net.yacy.search.Switchboard; +import net.yacy.server.serverObjects; +import net.yacy.server.serverSwitch; +import net.yacy.visualization.ImageViewer; + +/** + * Extends ViewImage behavior : add a specific favicon cache and use of a + * default image on loading error. + * + * @author luc + * + */ +public class ViewFavicon { + + /** Single instance of ImageViewer */ + private static final ImageViewer VIEWER = new ImageViewer(); + + /** Icons cache encoded as png */ + private static Map pngIconCache = new ConcurrentARC(1000, + Math.max(10, Math.min(32, WorkflowProcessor.availableCPU * 2))); + + /** Default icon local file */ + private static final String defaulticon = "htroot/env/grafics/dfltfvcn.ico"; + + /** + * Default icon encoded as png : we use a bvte array as it is thread-safe + * instead of a ByteBuffer in EncodedImage + */ + private static byte[] defaultPNGEncodedIcon = null; + + /** + * Try parsing image from post "url" parameter (authenticated users) or from + * "code" parameter (non authenticated users). When image could be parsed, + * try encoding to target format specified by header "EXT". When any error + * occurs, return default icon. + * + * @param header + * request header + * @param post + * post parameters + * @param env + * Switchboard instance + * @return an {@link EncodedImage} instance encoded in format specified in + * post, or an InputStream pointing to original image data. + */ + public static Object respond(final RequestHeader header, final serverObjects post, final serverSwitch env) { + + final Switchboard sb = (Switchboard) env; + String ext = header.get("EXT", null); + boolean isPNGTarget = "png".equalsIgnoreCase(ext); + + ImageInputStream imageInStream = null; + InputStream inStream = null; + byte[] resultBytes = null; + try { + /* Clear icon cache when running out of memory */ + if (MemoryControl.shortStatus()) { + pngIconCache.clear(); + } + + final boolean auth = Domains.isLocalhost(header.get(HeaderFramework.CONNECTION_PROP_CLIENTIP, "")) + || sb.verifyAuthentication(header); // handle access rights + + DigestURL url = VIEWER.parseURL(post, auth); + + final String normalizedURL = url.toNormalform(false); + + if (isPNGTarget) { + resultBytes = pngIconCache.get(normalizedURL); + } + /* Icon is not already in cache */ + if (resultBytes == null) { + String urlExt = MultiProtocolURL.getFileExtension(url.getFileName()); + if (ext != null && ext.equalsIgnoreCase(urlExt) && ImageViewer.isBrowserRendered(urlExt)) { + return VIEWER.openInputStream(post, sb.loader, auth, url); + } + /* + * When opening a file, the most efficient is to open + * ImageInputStream directly on file + */ + if (url.isFile()) { + imageInStream = ImageIO.createImageInputStream(url.getFSFile()); + } else { + inStream = VIEWER.openInputStream(post, sb.loader, auth, url); + imageInStream = ImageIO.createImageInputStream(inStream); + } + // read image + EncodedImage encodedIcon = VIEWER.parseAndScale(post, auth, url, ext, imageInStream); + + if (encodedIcon != null && !encodedIcon.getImage().isEmpty()) { + resultBytes = encodedIcon.getImage().getBytes(); + if (isPNGTarget && encodedIcon.getImage().length() <= 10240) { + /* Only store in cache icon images below 10KB, png encoded */ + pngIconCache.put(normalizedURL, resultBytes); + } + } + } + } catch (IOException e) { + ConcurrentLog.fine("ViewFavicon", "Error loading favicon, default one wille be used : " + e); + } finally { + /* + * imageInStream.close() method doesn't close source input stream + */ + if (inStream != null) { + try { + inStream.close(); + } catch (IOException ignored) { + } + } + if (resultBytes == null) { + /* + * I any error occured when loading icon, return default one + */ + if (ext == null || isPNGTarget) { + /* Load default icon only once */ + if (defaultPNGEncodedIcon == null) { + defaultPNGEncodedIcon = loadDefaultIcon(post, sb, ext); + } + resultBytes = defaultPNGEncodedIcon; + } else { + resultBytes = loadDefaultIcon(post, sb, ext); + } + } + + } + + return new ByteArrayInputStream(resultBytes); + } + + /** + * Load default icon and encode it to ext format + * + * @param post + * post parameters + * @param sb + * Switchboard instance + * @param ext + * target image format + * @return icon encoded bytes, empty if and exception occured when loading + * or rendering + */ + private static byte[] loadDefaultIcon(final serverObjects post, final Switchboard sb, String ext) { + byte[] resultBytes; + byte[] defaultBytes = new byte[0]; + try { + defaultBytes = FileUtils.read(new File(sb.getAppPath(), defaulticon)); + } catch (final IOException initicon) { + defaultBytes = new byte[0]; + } finally { + resultBytes = new EncodedImage(defaultBytes, ext, post.getBoolean("isStatic")).getImage().getBytes(); + } + return resultBytes; + } + +} diff --git a/htroot/ViewImage.java b/htroot/ViewImage.java index 45dd15c31..4625e9367 100644 --- a/htroot/ViewImage.java +++ b/htroot/ViewImage.java @@ -21,54 +21,28 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.MediaTracker; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.awt.image.Raster; -import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.Iterator; -import java.util.Map; import javax.imageio.ImageIO; -import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import net.yacy.cora.document.id.DigestURL; import net.yacy.cora.document.id.MultiProtocolURL; -import net.yacy.cora.federate.yacy.CacheStrategy; -import net.yacy.cora.protocol.ClientIdentification; import net.yacy.cora.protocol.Domains; import net.yacy.cora.protocol.HeaderFramework; import net.yacy.cora.protocol.RequestHeader; -import net.yacy.cora.storage.ConcurrentARC; -import net.yacy.cora.util.ConcurrentLog; -import net.yacy.data.InvalidURLLicenceException; -import net.yacy.data.URLLicense; import net.yacy.http.servlets.TemplateMissingParameterException; -import net.yacy.kelondro.util.FileUtils; -import net.yacy.kelondro.util.MemoryControl; -import net.yacy.kelondro.workflow.WorkflowProcessor; import net.yacy.peers.graphics.EncodedImage; -import net.yacy.repository.Blacklist.BlacklistType; -import net.yacy.repository.LoaderDispatcher; import net.yacy.search.Switchboard; import net.yacy.server.serverObjects; import net.yacy.server.serverSwitch; +import net.yacy.visualization.ImageViewer; public class ViewImage { - - private static Map iconcache = new ConcurrentARC(1000, - Math.max(10, Math.min(32, WorkflowProcessor.availableCPU * 2))); - - private static String defaulticon = "htroot/env/grafics/dfltfvcn.ico"; - private static byte[] defaulticonb = null; + + /** Single instance of ImageViewer */ + private static final ImageViewer VIEWER = new ImageViewer(); /** * Try parsing image from post "url" parameter (authenticated users) or from "code" parameter (non authenticated users). @@ -91,6 +65,7 @@ public class ViewImage { * Sould end in a HTTP 500 error whose processing is more * consistent across browsers than a response with zero content * bytes. + * @throws TemplateMissingParameterException when one required parameter is missing */ public static Object respond(final RequestHeader header, final serverObjects post, final serverSwitch env) throws IOException { @@ -100,501 +75,59 @@ public class ViewImage { if(post == null) { throw new TemplateMissingParameterException("please fill at least url or code parameter"); } - String urlString = post.get("url", ""); - final String urlLicense = post.get("code", ""); + String ext = header.get("EXT", null); final boolean auth = Domains.isLocalhost(header.get(HeaderFramework.CONNECTION_PROP_CLIENTIP, "")) || sb.verifyAuthentication(header); // handle access rights - DigestURL url = null; - if(auth) { - /* Authenticated user : rely on url parameter*/ - if (urlString.length() > 0) { - url = new DigestURL(urlString); - } else { - throw new TemplateMissingParameterException("missing required url parameter"); - } - } else { - /* Non authenticated user : rely on urlLicense parameter */ - if((urlLicense.length() > 0)) { - urlString = URLLicense.releaseLicense(urlLicense); - if (urlString != null) { - url = new DigestURL(urlString); - } else { // license is gone (e.g. released/remove in prev calls) - ConcurrentLog.fine("ViewImage", "image urlLicense not found key=" + urlLicense); - /* Caller is responsible for handling this with appropriate HTTP status code */ - throw new InvalidURLLicenceException(); - } - } else { - throw new TemplateMissingParameterException("missing required code parameter"); - } - } + DigestURL url = VIEWER.parseURL(post, auth); // get the image as stream - if (MemoryControl.shortStatus()) { - iconcache.clear(); - } - EncodedImage encodedImage = null; - Image image = iconcache.get(urlString); - if (image != null) { - encodedImage = new EncodedImage(image, ext, post.getBoolean("isStatic")); - } else { - - ImageInputStream imageInStream = null; - InputStream inStream = null; - try { - String urlExt = MultiProtocolURL.getFileExtension(url.getFileName()); - if (ext != null && ext.equalsIgnoreCase(urlExt) && isBrowserRendered(urlExt)) { - return openInputStream(post, sb.loader, auth, url); - } - /* - * When opening a file, the most efficient is to open - * ImageInputStream directly on file - */ - if (url.isFile()) { - imageInStream = ImageIO.createImageInputStream(url.getFSFile()); - } else { - inStream = openInputStream(post, sb.loader, auth, url); - imageInStream = ImageIO.createImageInputStream(inStream); - } - // read image - encodedImage = parseAndScale(post, auth, urlString, ext, imageInStream); - } catch(Exception e) { - /* Exceptions are not propagated here : many error causes are possible, network errors, - * incorrect or unsupported format, bad ImageIO plugin... - * Instead return an empty EncodedImage. Caller is responsible for handling this correctly (500 status code response) */ - - if ("favicon.ico".equalsIgnoreCase(url.getFileName())) { // but on missing favicon just present a default (occures frequently by call from searchitem.html) - // currently yacysearchitem assigns "hosturl/favicon.ico" (to look for the filename should not much interfere with other situatios) - if (defaulticonb == null) { // load the default icon once - try { - defaulticonb = FileUtils.read(new File(sb.getAppPath(), defaulticon)); - } catch (final IOException initicon) { - defaulticonb = new byte[0]; - } - } - encodedImage = new EncodedImage(defaulticonb, ext, post.getBoolean("isStatic")); - } else { - encodedImage = new EncodedImage(new byte[0], ext, post.getBoolean("isStatic")); - } - } finally { - /* - * imageInStream.close() method doesn't close source input - * stream - */ - if (inStream != null) { - try { - inStream.close(); - } catch (IOException ignored) { - } - } - } - } - - return encodedImage; - } - - /** - * Open input stream on image url using provided loader. All parameters must - * not be null. - * - * @param post - * post parameters. - * @param loader. - * Resources loader. - * @param auth - * true when user has credentials to load full images. - * @param url - * image url. - * @return an open input stream instance (don't forget to close it). - * @throws IOException - * when a read/write error occured. - */ - private static InputStream openInputStream(final serverObjects post, final LoaderDispatcher loader, - final boolean auth, DigestURL url) throws IOException { - InputStream inStream = null; - if (url != null) { - try { - String agentName = post.get("agentName", auth ? ClientIdentification.yacyIntranetCrawlerAgentName - : ClientIdentification.yacyInternetCrawlerAgentName); - ClientIdentification.Agent agent = ClientIdentification.getAgent(agentName); - inStream = loader.openInputStream(loader.request(url, false, true), CacheStrategy.IFEXIST, - BlacklistType.SEARCH, agent); - } catch (final IOException e) { - /** No need to log full stack trace (in most cases resource is not available because of a network error) */ - ConcurrentLog.fine("ViewImage", "cannot load image. URL : " + url.toNormalform(true)); - throw e; - } - } - if (inStream == null) { - throw new IOException("Input stream could no be open"); - } - return inStream; - } - - /** - * @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 source image to try to produce an EncodedImage instance - * eventually scaled and clipped depending on post parameters. When - * processed, imageInStream is closed. - * - * @param post - * request post parameters. Must not be null. - * @param auth - * true when access rigths are OK. - * @param urlString - * image source URL as String. Must not be null. - * @param ext - * target image file format. May be null. - * @param imageInStream - * open stream on image content. Must not be null. - * @return an EncodedImage instance. - * @throws IOException - * when image could not be parsed or encoded to specified format. - */ - protected static EncodedImage parseAndScale(serverObjects post, boolean auth, String urlString, String ext, - ImageInputStream imageInStream) throws IOException { EncodedImage encodedImage; - // BufferedImage image = ImageIO.read(imageInStream); - Iterator readers = ImageIO.getImageReaders(imageInStream); - if (!readers.hasNext()) { - try { - /* When no reader can be found, we have to close the stream */ - imageInStream.close(); - } catch (IOException ignoredException) { + ImageInputStream imageInStream = null; + InputStream inStream = null; + try { + String urlExt = MultiProtocolURL.getFileExtension(url.getFileName()); + if (ext != null && ext.equalsIgnoreCase(urlExt) && ImageViewer.isBrowserRendered(urlExt)) { + return VIEWER.openInputStream(post, sb.loader, auth, url); } - String errorMessage = "Image format (" + ext + ") is not supported."; - ConcurrentLog.fine("ViewImage", errorMessage + "Image URL : " + urlString); /* - * Throw an exception, wich will end in a HTTP 500 response, better - * handled by browsers than an empty image + * When opening a file, the most efficient is to open + * ImageInputStream directly on file */ - throw new IOException(errorMessage); - } - ImageReader reader = readers.next(); - reader.setInput(imageInStream, true, true); - - int maxwidth = post.getInt("maxwidth", 0); - int maxheight = post.getInt("maxheight", 0); - final boolean quadratic = post.containsKey("quadratic"); - boolean isStatic = post.getBoolean("isStatic"); - BufferedImage image = null; - boolean returnRaw = true; - if (!auth || maxwidth != 0 || maxheight != 0) { - - // find original size - final int originWidth = reader.getWidth(0); - final int originHeigth = reader.getHeight(0); - - // in case of not-authorized access shrink the image to - // prevent - // copyright problems, so that images are not larger than - // thumbnails - Dimension maxDimensions = calculateMaxDimensions(auth, originWidth, originHeigth, maxwidth, maxheight); - - // if a quadratic flag is set, we cut the image out to be in - // quadratic shape - int w = originWidth; - int h = originHeigth; - if (quadratic && originWidth != originHeigth) { - Rectangle square = getMaxSquare(originHeigth, originWidth); - h = square.height; - w = square.width; - } - - Dimension finalDimensions = calculateDimensions(w, h, maxDimensions); - - if (originWidth != finalDimensions.width || originHeigth != finalDimensions.height) { - returnRaw = false; - image = readImage(reader); - if (quadratic && originWidth != originHeigth) { - image = makeSquare(image); - } - image = scale(finalDimensions.width, finalDimensions.height, image); - } - if (finalDimensions.width == 16 && finalDimensions.height == 16) { - // this might be a favicon, store image to cache for - // faster - // re-load later on - if (image == null) { - returnRaw = false; - image = readImage(reader); - } - iconcache.put(urlString, image); - } - } - /* Image do not need to be scaled or cropped */ - if (returnRaw) { - if (!reader.getFormatName().equalsIgnoreCase(ext) || imageInStream.getFlushedPosition() != 0) { - /* - * image parsing and reencoding is only needed when source image - * and target formats differ, or when first bytes have been discarded - */ - returnRaw = false; - image = readImage(reader); + if (url.isFile()) { + imageInStream = ImageIO.createImageInputStream(url.getFSFile()); + } else { + inStream = VIEWER.openInputStream(post, sb.loader, auth, url); + imageInStream = ImageIO.createImageInputStream(inStream); } - } - if (returnRaw) { - byte[] imageData = readRawImage(imageInStream); - encodedImage = new EncodedImage(imageData, ext, isStatic); - } else { + // read image + encodedImage = VIEWER.parseAndScale(post, auth, url, ext, imageInStream); + } catch (Exception e) { /* - * An error can still occur when transcoding from buffered image to - * target ext : in that case EncodedImage.getImage() is empty. + * Exceptions are not propagated here : many error causes are + * possible, network errors, incorrect or unsupported format, bad + * ImageIO plugin... Instead return an empty EncodedImage. Caller is + * responsible for handling this correctly (500 status code + * response) */ - encodedImage = new EncodedImage(image, ext, isStatic); - if (encodedImage.getImage().length() == 0) { - String errorMessage = "Image could not be encoded to format : " + ext; - ConcurrentLog.fine("ViewImage", errorMessage + ". Image URL : " + urlString); - throw new IOException(errorMessage); - } - } - - return encodedImage; - } - - /** - * Read image using specified reader and close ImageInputStream source. - * Input must have bean set before using - * {@link ImageReader#setInput(Object)} - * - * @param reader - * image reader. Must not be null. - * @return buffered image - * @throws IOException - * when an error occured - */ - private static BufferedImage readImage(ImageReader reader) throws IOException { - BufferedImage image; - try { - image = reader.read(0); + encodedImage = new EncodedImage(new byte[0], ext, post.getBoolean("isStatic")); } finally { - reader.dispose(); - Object input = reader.getInput(); - if (input instanceof ImageInputStream) { - try { - ((ImageInputStream) input).close(); - } catch (IOException ignoredException) { - } - } - } - return image; - } - - /** - * Read image data without parsing. - * - * @param inStream - * image source. Must not be null. First bytes must not have been marked discarded ({@link ImageInputStream#getFlushedPosition()} must be zero) - * @return image data as bytes - * @throws IOException - * when a read/write error occured. - */ - private static byte[] readRawImage(ImageInputStream inStream) throws IOException { - byte[] buffer = new byte[4096]; - int l = 0; - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - inStream.seek(0); - try { - while ((l = inStream.read(buffer)) >= 0) { - outStream.write(buffer, 0, l); - } - return outStream.toByteArray(); - } finally { - try { - inStream.close(); - } catch (IOException ignored) { - } - } - } - - /** - * Calculate image dimensions from image original dimensions, max - * dimensions, and target dimensions. - * - * @return dimensions to render image - */ - 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); - final double vs = (originHeight <= max.height) ? 1.0 : ((double) max.height) / ((double) originHeight); - final double scale = Math.min(hs, vs); - // if (!auth) scale = Math.min(scale, 0.6); // this is for copyright - // purpose - if (scale < 1.0) { - resultWidth = Math.max(1, (int) (originWidth * scale)); - resultHeight = Math.max(1, (int) (originHeight * scale)); - } else { - resultWidth = Math.max(1, originWidth); - resultHeight = Math.max(1, originHeight); - } - - } else { - // do not scale - resultWidth = originWidth; - resultHeight = originHeight; - } - return new Dimension(resultWidth, resultHeight); - } - - /** - * Calculate image maximum dimentions from original and specified maximum - * dimensions - * - * @param auth - * true when acces rigths are OK. - * @return maximum dimensions to render image - */ - protected static Dimension calculateMaxDimensions(final boolean auth, final int originWidth, final int originHeight, - final int maxWidth, final int maxHeight) { - int resultWidth; - int resultHeight; - // in case of not-authorized access shrink the image to prevent - // copyright problems, so that images are not larger than thumbnails - if (auth) { - resultWidth = (maxWidth == 0) ? originWidth : maxWidth; - resultHeight = (maxHeight == 0) ? originHeight : maxHeight; - } else if ((originWidth > 16) || (originHeight > 16)) { - resultWidth = Math.min(96, originWidth); - resultHeight = Math.min(96, originHeight); - } else { - resultWidth = 16; - resultHeight = 16; - } - return new Dimension(resultWidth, resultHeight); - } - - /** - * Scale image to specified dimensions - * - * @param width - * target width - * @param height - * target height - * @param image - * image to scale. Must not be null. - * @return a scaled image - */ - protected static BufferedImage scale(final int width, final int height, final BufferedImage image) { - // compute scaled image - Image scaled = image.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING); - final MediaTracker mediaTracker = new MediaTracker(new Container()); - mediaTracker.addImage(scaled, 0); - try { - mediaTracker.waitForID(0); - } catch (final InterruptedException e) { - } - - // make a BufferedImage out of that - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - try { - result.createGraphics().drawImage(scaled, 0, 0, width, height, null); - // check outcome - final Raster raster = result.getData(); - int[] pixel = new int[raster.getSampleModel().getNumBands()]; - pixel = raster.getPixel(0, 0, pixel); - } catch (final Exception e) { /* - * Exception may be caused by source image color model : try now to - * convert to RGB before scaling + * imageInStream.close() method doesn't close source input stream */ - try { - BufferedImage converted = EncodedImage.convertToRGB(image); - scaled = converted.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING); - mediaTracker.addImage(scaled, 1); + if (inStream != null) { try { - mediaTracker.waitForID(1); - } catch (final InterruptedException e2) { + inStream.close(); + } catch (IOException ignored) { } - result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - result.createGraphics().drawImage(scaled, 0, 0, width, height, null); - - // check outcome - final Raster raster = result.getData(); - int[] pixel = new int[result.getSampleModel().getNumBands()]; - pixel = raster.getPixel(0, 0, pixel); - } catch (Exception e2) { - result = image; } - - ConcurrentLog.fine("ViewImage", "Image could not be scaled"); } - return result; - } - /** - * - * @param h - * image height - * @param w - * image width - * @return max square area fitting inside dimensions - */ - protected static Rectangle getMaxSquare(final int h, final int w) { - Rectangle square; - if (w > h) { - final int offset = (w - h) / 2; - square = new Rectangle(offset, 0, h, h); - } else { - final int offset = (h - w) / 2; - square = new Rectangle(0, offset, w, w); - } - return square; + return encodedImage; } - /** - * Crop image to make a square - * - * @param image - * image to crop - * @return - */ - protected static BufferedImage makeSquare(BufferedImage image) { - final int w = image.getWidth(); - final int h = image.getHeight(); - if (w > h) { - final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = dst.createGraphics(); - final int offset = (w - h) / 2; - try { - g.drawImage(image, 0, 0, h - 1, h - 1, offset, 0, h + offset, h - 1, null); - } finally { - g.dispose(); - } - image = dst; - } else { - final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = dst.createGraphics(); - final int offset = (h - w) / 2; - try { - g.drawImage(image, 0, 0, w - 1, w - 1, 0, offset, w - 1, w + offset, null); - } finally { - g.dispose(); - } - image = dst; - } - return image; - } + } diff --git a/htroot/yacysearchitem.java b/htroot/yacysearchitem.java index cce332601..3ba1d727b 100644 --- a/htroot/yacysearchitem.java +++ b/htroot/yacysearchitem.java @@ -68,6 +68,7 @@ import net.yacy.server.serverObjects; import net.yacy.server.serverSwitch; import net.yacy.utils.crypt; import net.yacy.utils.nxTools; +import net.yacy.visualization.ImageViewer; public class yacysearchitem { @@ -383,10 +384,14 @@ public class yacysearchitem { * is null */ private static String processFaviconURL(final boolean authenticated, DigestURL faviconURL) { + final String iconUrlExt = MultiProtocolURL.getFileExtension(faviconURL.getFileName()); + /* Image format ouput for ViewFavicon servlet : default is png, except with gif and svg icons */ + final String viewFaviconExt = !iconUrlExt.isEmpty() && ImageViewer.isBrowserRendered(iconUrlExt) ? iconUrlExt : "png"; + /* Only use licence code for non authentified users. For authenticated users licence would never be released and would unnecessarily fill URLLicense.permissions. */ StringBuilder contentFaviconURL = new StringBuilder(); if (faviconURL != null) { - contentFaviconURL.append("ViewImage.png?width=16&height=16&isStatic=true"); + contentFaviconURL.append("ViewFavicon.").append(viewFaviconExt).append("?maxwidth=16&maxheight=16&isStatic=true&quadratic"); if (authenticated) { contentFaviconURL.append("&url=").append(faviconURL.toNormalform(true)); } else { @@ -418,7 +423,7 @@ public class yacysearchitem { 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"; + final String viewImageExt = !imageUrlExt.isEmpty() && ImageViewer.isBrowserRendered(imageUrlExt) ? imageUrlExt : "png"; /* Thumb URL */ StringBuilder thumbURLBuilder = new StringBuilder("ViewImage.").append(viewImageExt).append("?maxwidth=") .append(DEFAULT_IMG_WIDTH).append("&maxheight=").append(DEFAULT_IMG_HEIGHT) @@ -449,7 +454,7 @@ public class yacysearchitem { /* 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 (ImageViewer.isBrowserRendered(imageUrlExt)) { if (image.width > image.height) { /* Landscape orientation */ itemWidth = ""; diff --git a/source/net/yacy/kelondro/data/meta/URIMetadataNode.java b/source/net/yacy/kelondro/data/meta/URIMetadataNode.java index 785ffe62a..e0eaf56e1 100644 --- a/source/net/yacy/kelondro/data/meta/URIMetadataNode.java +++ b/source/net/yacy/kelondro/data/meta/URIMetadataNode.java @@ -608,7 +608,7 @@ public class URIMetadataNode extends SolrDocument /* implements Comparable. + */ + +package net.yacy.visualization; + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.MediaTracker; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.Iterator; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import net.yacy.cora.document.id.DigestURL; +import net.yacy.cora.document.id.MultiProtocolURL; +import net.yacy.cora.federate.yacy.CacheStrategy; +import net.yacy.cora.protocol.ClientIdentification; +import net.yacy.cora.util.ConcurrentLog; +import net.yacy.data.InvalidURLLicenceException; +import net.yacy.data.URLLicense; +import net.yacy.http.servlets.TemplateMissingParameterException; +import net.yacy.peers.graphics.EncodedImage; +import net.yacy.repository.Blacklist.BlacklistType; +import net.yacy.repository.LoaderDispatcher; +import net.yacy.server.serverObjects; + +/** + * Provides methods for image or favicon viewing in YaCy servlets. + * @author luc + * + */ +public class ImageViewer { + + /** + * Try to get image URL from parameters. + * @param post post parameters. Must not be null. + * @param auth true when current user is authenticated + * @return DigestURL instance + * @throws MalformedURLException when url is malformed + * @throws TemplateMissingParameterException when urlString or urlLicense is missing (the one needed depends on auth) + */ + public DigestURL parseURL(final serverObjects post, final boolean auth) + throws MalformedURLException { + final String urlString = post.get("url", ""); + final String urlLicense = post.get("code", ""); + DigestURL url; + if(auth) { + /* Authenticated user : rely on url parameter*/ + if (urlString.length() > 0) { + url = new DigestURL(urlString); + } else { + throw new TemplateMissingParameterException("missing required url parameter"); + } + } else { + /* Non authenticated user : rely on urlLicense parameter */ + if((urlLicense.length() > 0)) { + String licensedURL = URLLicense.releaseLicense(urlLicense); + if (licensedURL != null) { + url = new DigestURL(licensedURL); + } else { // license is gone (e.g. released/remove in prev calls) + ConcurrentLog.fine("ImageViewer", "image urlLicense not found key=" + urlLicense); + /* Caller is responsible for handling this with appropriate HTTP status code */ + throw new InvalidURLLicenceException(); + } + } else { + throw new TemplateMissingParameterException("missing required code parameter"); + } + } + return url; + } + + /** + * Open input stream on image url using provided loader. All parameters must + * not be null. + * + * @param post + * post parameters. + * @param loader. + * Resources loader. + * @param auth + * true when user has credentials to load full images. + * @param url + * image url. + * @return an open input stream instance (don't forget to close it). + * @throws IOException + * when a read/write error occured. + */ + public InputStream openInputStream(final serverObjects post, final LoaderDispatcher loader, + final boolean auth, DigestURL url) throws IOException { + InputStream inStream = null; + if (url != null) { + try { + String agentName = post.get("agentName", auth ? ClientIdentification.yacyIntranetCrawlerAgentName + : ClientIdentification.yacyInternetCrawlerAgentName); + ClientIdentification.Agent agent = ClientIdentification.getAgent(agentName); + inStream = loader.openInputStream(loader.request(url, false, true), CacheStrategy.IFEXIST, + BlacklistType.SEARCH, agent); + } catch (final IOException e) { + /** No need to log full stack trace (in most cases resource is not available because of a network error) */ + ConcurrentLog.fine("ImageViewer", "cannot load image. URL : " + url.toNormalform(true)); + throw e; + } + } + if (inStream == null) { + throw new IOException("Input stream could no be open"); + } + return inStream; + } + + /** + * @param formatName + * informal file format name. For example : "png". + * @return true when image format will be rendered by browser and not by a YaCy service + */ + 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 source image to try to produce an EncodedImage instance + * eventually scaled and clipped depending on post parameters. When + * processed, imageInStream is closed. + * + * @param post + * request post parameters. Must not be null. + * @param auth + * true when access rigths are OK. + * @param url + * image source URL. Must not be null. + * @param ext + * target image file format. May be null. + * @param imageInStream + * open stream on image content. Must not be null. + * @return an EncodedImage instance. + * @throws IOException + * when image could not be parsed or encoded to specified format. + */ + public EncodedImage parseAndScale(serverObjects post, boolean auth, DigestURL url, String ext, + ImageInputStream imageInStream) throws IOException { + EncodedImage encodedImage; + + // BufferedImage image = ImageIO.read(imageInStream); + Iterator readers = ImageIO.getImageReaders(imageInStream); + if (!readers.hasNext()) { + try { + /* When no reader can be found, we have to close the stream */ + imageInStream.close(); + } catch (IOException ignoredException) { + } + String urlString = url.toNormalform(false); + String errorMessage = "Image format (" + MultiProtocolURL.getFileExtension(urlString) + ") is not supported."; + ConcurrentLog.fine("ImageViewer", errorMessage + "Image URL : " + urlString); + /* + * Throw an exception, wich will end in a HTTP 500 response, better + * handled by browsers than an empty image + */ + throw new IOException(errorMessage); + } + ImageReader reader = readers.next(); + reader.setInput(imageInStream, true, true); + + int maxwidth = post.getInt("maxwidth", 0); + int maxheight = post.getInt("maxheight", 0); + final boolean quadratic = post.containsKey("quadratic"); + boolean isStatic = post.getBoolean("isStatic"); + BufferedImage image = null; + boolean returnRaw = true; + if (!auth || maxwidth != 0 || maxheight != 0) { + + // find original size + final int originWidth = reader.getWidth(0); + final int originHeigth = reader.getHeight(0); + + // in case of not-authorized access shrink the image to + // prevent + // copyright problems, so that images are not larger than + // thumbnails + Dimension maxDimensions = calculateMaxDimensions(auth, originWidth, originHeigth, maxwidth, maxheight); + + // if a quadratic flag is set, we cut the image out to be in + // quadratic shape + int w = originWidth; + int h = originHeigth; + if (quadratic && originWidth != originHeigth) { + Rectangle square = getMaxSquare(originHeigth, originWidth); + h = square.height; + w = square.width; + } + + Dimension finalDimensions = calculateDimensions(w, h, maxDimensions); + + if (originWidth != finalDimensions.width || originHeigth != finalDimensions.height) { + returnRaw = false; + image = readImage(reader); + if (quadratic && originWidth != originHeigth) { + image = makeSquare(image); + } + image = scale(finalDimensions.width, finalDimensions.height, image); + } + } + /* Image do not need to be scaled or cropped */ + if (returnRaw) { + if (!reader.getFormatName().equalsIgnoreCase(ext) || imageInStream.getFlushedPosition() != 0) { + /* + * image parsing and reencoding is only needed when source image + * and target formats differ, or when first bytes have been discarded + */ + returnRaw = false; + image = readImage(reader); + } + } + if (returnRaw) { + byte[] imageData = readRawImage(imageInStream); + encodedImage = new EncodedImage(imageData, ext, isStatic); + } else { + /* + * An error can still occur when transcoding from buffered image to + * target ext : in that case EncodedImage.getImage() is empty. + */ + encodedImage = new EncodedImage(image, ext, isStatic); + if (encodedImage.getImage().length() == 0) { + String errorMessage = "Image could not be encoded to format : " + ext; + ConcurrentLog.fine("ImageViewer", errorMessage + ". Image URL : " + url.toNormalform(false)); + throw new IOException(errorMessage); + } + } + + return encodedImage; + } + + /** + * Read image using specified reader and close ImageInputStream source. + * Input must have bean set before using + * {@link ImageReader#setInput(Object)} + * + * @param reader + * image reader. Must not be null. + * @return buffered image + * @throws IOException + * when an error occured + */ + private BufferedImage readImage(ImageReader reader) throws IOException { + BufferedImage image; + try { + image = reader.read(0); + } finally { + reader.dispose(); + Object input = reader.getInput(); + if (input instanceof ImageInputStream) { + try { + ((ImageInputStream) input).close(); + } catch (IOException ignoredException) { + } + } + } + return image; + } + + /** + * Read image data without parsing. + * + * @param inStream + * image source. Must not be null. First bytes must not have been marked discarded ({@link ImageInputStream#getFlushedPosition()} must be zero) + * @return image data as bytes + * @throws IOException + * when a read/write error occured. + */ + private byte[] readRawImage(ImageInputStream inStream) throws IOException { + byte[] buffer = new byte[4096]; + int l = 0; + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + inStream.seek(0); + try { + while ((l = inStream.read(buffer)) >= 0) { + outStream.write(buffer, 0, l); + } + return outStream.toByteArray(); + } finally { + try { + inStream.close(); + } catch (IOException ignored) { + } + } + } + + /** + * Calculate image dimensions from image original dimensions, max + * dimensions, and target dimensions. + * + * @return dimensions to render image + */ + protected 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); + final double vs = (originHeight <= max.height) ? 1.0 : ((double) max.height) / ((double) originHeight); + final double scale = Math.min(hs, vs); + // if (!auth) scale = Math.min(scale, 0.6); // this is for copyright + // purpose + if (scale < 1.0) { + resultWidth = Math.max(1, (int) (originWidth * scale)); + resultHeight = Math.max(1, (int) (originHeight * scale)); + } else { + resultWidth = Math.max(1, originWidth); + resultHeight = Math.max(1, originHeight); + } + + } else { + // do not scale + resultWidth = originWidth; + resultHeight = originHeight; + } + return new Dimension(resultWidth, resultHeight); + } + + /** + * Calculate image maximum dimentions from original and specified maximum + * dimensions + * + * @param auth + * true when acces rigths are OK. + * @return maximum dimensions to render image + */ + protected Dimension calculateMaxDimensions(final boolean auth, final int originWidth, final int originHeight, + final int maxWidth, final int maxHeight) { + int resultWidth; + int resultHeight; + // in case of not-authorized access shrink the image to prevent + // copyright problems, so that images are not larger than thumbnails + if (auth) { + resultWidth = (maxWidth == 0) ? originWidth : maxWidth; + resultHeight = (maxHeight == 0) ? originHeight : maxHeight; + } else if ((originWidth > 16) || (originHeight > 16)) { + resultWidth = Math.min(96, originWidth); + resultHeight = Math.min(96, originHeight); + } else { + resultWidth = 16; + resultHeight = 16; + } + return new Dimension(resultWidth, resultHeight); + } + + /** + * Scale image to specified dimensions + * + * @param width + * target width + * @param height + * target height + * @param image + * image to scale. Must not be null. + * @return a scaled image + */ + public BufferedImage scale(final int width, final int height, final BufferedImage image) { + // compute scaled image + Image scaled = image.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING); + final MediaTracker mediaTracker = new MediaTracker(new Container()); + mediaTracker.addImage(scaled, 0); + try { + mediaTracker.waitForID(0); + } catch (final InterruptedException e) { + } + + // make a BufferedImage out of that + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + try { + result.createGraphics().drawImage(scaled, 0, 0, width, height, null); + // check outcome + final Raster raster = result.getData(); + int[] pixel = new int[raster.getSampleModel().getNumBands()]; + pixel = raster.getPixel(0, 0, pixel); + } catch (final Exception e) { + /* + * Exception may be caused by source image color model : try now to + * convert to RGB before scaling + */ + try { + BufferedImage converted = EncodedImage.convertToRGB(image); + scaled = converted.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING); + mediaTracker.addImage(scaled, 1); + try { + mediaTracker.waitForID(1); + } catch (final InterruptedException e2) { + } + result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + result.createGraphics().drawImage(scaled, 0, 0, width, height, null); + + // check outcome + final Raster raster = result.getData(); + int[] pixel = new int[result.getSampleModel().getNumBands()]; + pixel = raster.getPixel(0, 0, pixel); + } catch (Exception e2) { + result = image; + } + + ConcurrentLog.fine("ImageViewer", "Image could not be scaled"); + } + return result; + } + + /** + * + * @param h + * image height + * @param w + * image width + * @return max square area fitting inside dimensions + */ + public Rectangle getMaxSquare(final int h, final int w) { + Rectangle square; + if (w > h) { + final int offset = (w - h) / 2; + square = new Rectangle(offset, 0, h, h); + } else { + final int offset = (h - w) / 2; + square = new Rectangle(0, offset, w, w); + } + return square; + } + + /** + * Crop image to make a square + * + * @param image + * image to crop + * @return + */ + public BufferedImage makeSquare(BufferedImage image) { + final int w = image.getWidth(); + final int h = image.getHeight(); + if (w > h) { + final BufferedImage dst = new BufferedImage(h, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = dst.createGraphics(); + final int offset = (w - h) / 2; + try { + g.drawImage(image, 0, 0, h - 1, h - 1, offset, 0, h + offset, h - 1, null); + } finally { + g.dispose(); + } + image = dst; + } else { + final BufferedImage dst = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = dst.createGraphics(); + final int offset = (h - w) / 2; + try { + g.drawImage(image, 0, 0, w - 1, w - 1, 0, offset, w - 1, w + offset, null); + } finally { + g.dispose(); + } + image = dst; + } + return image; + } +} diff --git a/test/ViewImagePerfTest.java b/test/ImageViewerPerfTest.java similarity index 89% rename from test/ViewImagePerfTest.java rename to test/ImageViewerPerfTest.java index 5a13e30a3..6f758164c 100755 --- a/test/ViewImagePerfTest.java +++ b/test/ImageViewerPerfTest.java @@ -1,21 +1,4 @@ -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import javax.imageio.ImageIO; -import javax.imageio.stream.ImageInputStream; - -import net.yacy.cora.util.ConcurrentLog; -import net.yacy.peers.graphics.EncodedImage; -import net.yacy.server.serverObjects; - -// ViewImagePerfTest.java +// ImageViewerPerfTest.java // ----------------------- // part of YaCy // (C) by Michael Peter Christen; mc@yacy.net @@ -37,13 +20,33 @@ import net.yacy.server.serverObjects; // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; + +import net.yacy.cora.document.id.DigestURL; +import net.yacy.cora.util.ConcurrentLog; +import net.yacy.peers.graphics.EncodedImage; +import net.yacy.server.serverObjects; + + + /** - * Test to measure image render performance by ViewImage + * Test to measure image render performance by * * @author luc * */ -public class ViewImagePerfTest extends ViewImageTest { +public class ImageViewerPerfTest extends ImageViewerTest { /** Default minimum measurement time */ private static final int DEFAULT_MIN_MEASURE_TIME = 10; @@ -56,7 +59,7 @@ public class ViewImagePerfTest extends ViewImageTest { * main parameters : args[7] may contain minimum measurement time * in secondes. Default : 10. */ - public ViewImagePerfTest(String args[]) { + public ImageViewerPerfTest(String args[]) { this.minMeasureTime = getMinMeasurementTime(args); } @@ -87,7 +90,7 @@ public class ViewImagePerfTest extends ViewImageTest { * @param outDir * output directory * @param post - * ViewImage post parameters + * post parameters * @param failures * map failed file urls to eventual exception * @param inFile @@ -100,7 +103,7 @@ public class ViewImagePerfTest extends ViewImageTest { File inFile) throws IOException { /* Delete eventual previous result file */ System.out - .println("Measuring ViewImage render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext); + .println("Measuring render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext); File outFile = new File(outDir, inFile.getName() + "." + ext); if (outFile.exists()) { outFile.delete(); @@ -115,7 +118,7 @@ public class ViewImagePerfTest extends ViewImageTest { beginTime = System.nanoTime(); ImageInputStream inStream = ImageIO.createImageInputStream(inFile); try { - img = ViewImage.parseAndScale(post, true, urlString, ext, inStream); + img = this.VIEWER.parseAndScale(post, true, new DigestURL(urlString), ext, inStream); } catch (Exception e) { error = e; } @@ -131,7 +134,7 @@ public class ViewImagePerfTest extends ViewImageTest { } PrintWriter resultsWriter = new PrintWriter(new FileWriter(new File(outDir, "results_perfs.txt"), true)); try { - writeMessage("Measured ViewImage render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext, + writeMessage("Measured render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext, resultsWriter); if(img == null) { writeMessage("Image could not be rendered! Measurement show time needed to read and parse image data until error detection.", resultsWriter); @@ -176,7 +179,7 @@ public class ViewImagePerfTest extends ViewImageTest { *
  • args[1] : output format name (for example : "jpg") for * rendered image. Defaut : "png".
  • *
  • args[2] : ouput folder URL. Default : - * "[system tmp dir]/ViewImageTest".
  • + * "[system tmp dir]/Test". *
  • args[3] : max width (in pixels) for rendered image. May be * set to zero to specify no max width. Default : no value.
  • *
  • args[4] : max height (in pixels) for rendered image. May @@ -194,7 +197,7 @@ public class ViewImagePerfTest extends ViewImageTest { * when a read/write error occured */ public static void main(String args[]) throws IOException { - ViewImagePerfTest test = new ViewImagePerfTest(args); + ImageViewerPerfTest test = new ImageViewerPerfTest(args); File inFile = test.getInputURL(args); String ext = test.getEncodingExt(args); File outDir = test.getOuputDir(args); @@ -207,10 +210,10 @@ public class ViewImagePerfTest extends ViewImageTest { inFiles = new File[1]; inFiles[0] = inFile; System.out.println( - "Measuring ViewImage render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext); + "Measuring render with file : " + inFile.getAbsolutePath() + " encoded To : " + ext); } else if (inFile.isDirectory()) { inFiles = inFile.listFiles(); - System.out.println("Measuring ViewImage render with files in folder : " + inFile.getAbsolutePath() + System.out.println("Measuring render with files in folder : " + inFile.getAbsolutePath() + " encoded To : " + ext); } else { inFiles = new File[0]; diff --git a/test/ViewImageTest.java b/test/ImageViewerTest.java similarity index 92% rename from test/ViewImageTest.java rename to test/ImageViewerTest.java index a80a7d588..894067f5e 100755 --- a/test/ViewImageTest.java +++ b/test/ImageViewerTest.java @@ -1,23 +1,4 @@ -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; - -import javax.imageio.ImageIO; -import javax.imageio.stream.ImageInputStream; - -import net.yacy.cora.util.ConcurrentLog; -import net.yacy.peers.graphics.EncodedImage; -import net.yacy.server.serverObjects; - -// ViewImageTest.java +// ImageViewerTest.java // ----------------------- // part of YaCy // (C) by Michael Peter Christen; mc@yacy.net @@ -39,19 +20,43 @@ import net.yacy.server.serverObjects; // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; + +import net.yacy.cora.document.id.DigestURL; +import net.yacy.cora.util.ConcurrentLog; +import net.yacy.peers.graphics.EncodedImage; +import net.yacy.server.serverObjects; +import net.yacy.visualization.ImageViewer; + /** - * Test rendering of one or more image files by ViewImage + * Test rendering of one or more image files by ImageViewer * * @author luc * */ -public class ViewImageTest { +public class ImageViewerTest { /** Default image */ private static final String DEFAULT_IMG_RESOURCES = "/viewImageTest/test"; /** Default output encoding format */ private static final String DEFAULT_OUT_EXT = "png"; + + /** Viewer instance */ + protected final ImageViewer VIEWER = new ImageViewer(); /** * @param args @@ -65,7 +70,7 @@ public class ViewImageTest { if (args != null && args.length > 0) { fileURL = args[0]; } else { - URL defaultURL = ViewImageTest.class.getResource(DEFAULT_IMG_RESOURCES); + URL defaultURL = ImageViewerTest.class.getResource(DEFAULT_IMG_RESOURCES); if (defaultURL == null) { throw new IllegalArgumentException("File not found : " + DEFAULT_IMG_RESOURCES); } @@ -97,7 +102,7 @@ public class ViewImageTest { } /** - * Build post parameters to use with ViewImage + * Build post parameters to use with ImageViewer * * @param args * main parameters : args[3] and args[4] may respectively contain @@ -238,7 +243,7 @@ public class ViewImageTest { * @param outDir * output directory * @param post - * ViewImage post parameters + * ImageViewer post parameters * @param inFiles * files or directories to process * @param processedFiles @@ -269,7 +274,7 @@ public class ViewImageTest { * parameters must not be null. * @param ext output encoding image format * @param outDir output directory - * @param post ViewImage post parameters + * @param post ImageViewer post parameters * @param failures map failed file urls to eventual exception * @param inFile file image to process * @throws IOException when an read/write error occured @@ -287,7 +292,7 @@ public class ViewImageTest { EncodedImage img = null; Throwable error = null; try { - img = ViewImage.parseAndScale(post, true, urlString, ext, inStream); + img = this.VIEWER.parseAndScale(post, true, new DigestURL(urlString), ext, inStream); } catch (Throwable e) { error = e; } @@ -322,7 +327,7 @@ public class ViewImageTest { *
  • args[1] : output format name (for example : "jpg") for * rendered image. Defaut : "png".
  • *
  • args[2] : ouput folder URL. Default : - * "[system tmp dir]/ViewImageTest".
  • + * "[system tmp dir]/ImageViewerTest". *
  • args[3] : max width (in pixels) for rendered image. May be * set to zero to specify no max width. Default : no value.
  • *
  • args[4] : max height (in pixels) for rendered image. May @@ -338,7 +343,7 @@ public class ViewImageTest { * when a read/write error occured */ public static void main(String args[]) throws IOException { - ViewImageTest test = new ViewImageTest(); + ImageViewerTest test = new ImageViewerTest(); File inFile = test.getInputURL(args); String ext = test.getEncodingExt(args); File outDir = test.getOuputDir(args); @@ -350,11 +355,11 @@ public class ViewImageTest { if (inFile.isFile()) { inFiles = new File[1]; inFiles[0] = inFile; - System.out.println("Testing ViewImage rendering with input file : " + inFile.getAbsolutePath() + System.out.println("Testing ImageViewer rendering with input file : " + inFile.getAbsolutePath() + " encoded To : " + ext); } else if (inFile.isDirectory()) { inFiles = inFile.listFiles(); - System.out.println("Testing ViewImage rendering with input files in folder : " + inFile.getAbsolutePath() + System.out.println("Testing ImageViewer rendering with input files in folder : " + inFile.getAbsolutePath() + " encoded To : " + ext); } else { inFiles = new File[0]; diff --git a/test/java/yacysearchitemTest.java b/test/java/yacysearchitemTest.java index ef28d519e..c79a94030 100644 --- a/test/java/yacysearchitemTest.java +++ b/test/java/yacysearchitemTest.java @@ -155,6 +155,26 @@ public class yacysearchitemTest { Assert.assertNotNull(faviconURL); Assert.assertEquals("http://somehost.org/static/images/favicon.ico", faviconURL.toNormalform(false)); } + + /** + * One non-standard icon with no size + * + * @throws MalformedURLException + */ + @Test + public final void testGetFaviconURLNonStandardNoSize() throws MalformedURLException { + URIMetadataNode metadataNode = new URIMetadataNode(new DigestURL("http://somehost.org")); + metadataNode.setField(CollectionSchema.icons_urlstub_sxt.getSolrFieldName(), + new String[] { "somehost.org/static/images/favicon.png" }); + List protocols = CollectionConfiguration + .protocolList2indexedList(Arrays.asList(new String[] { "http" })); + metadataNode.setField(CollectionSchema.icons_protocol_sxt.getSolrFieldName(), protocols); + metadataNode.setField(CollectionSchema.icons_rel_sxt.getSolrFieldName(), new String[] { "appel-touch-icon" }); + + DigestURL faviconURL = yacysearchitem.getFaviconURL(metadataNode, new Dimension(32, 32)); + Assert.assertNotNull(faviconURL); + Assert.assertEquals("http://somehost.org/static/images/favicon.png", faviconURL.toNormalform(false)); + } /** * No icon in document