/* GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Contact info: lobochief@users.sourceforge.net */ /* * Created on Nov 19, 2005 */ package org.lobobrowser.html.renderer; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import javax.swing.SwingUtilities; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.lobobrowser.html.domimpl.HTMLElementImpl; import org.lobobrowser.html.domimpl.HTMLImageElementImpl; import org.lobobrowser.html.domimpl.ImageEvent; import org.lobobrowser.html.domimpl.ImageListener; import org.lobobrowser.html.style.HtmlValues; import org.lobobrowser.ua.ImageResponse; import cz.vutbr.web.css.CSSProperty.VerticalAlign; class ImgControl extends BaseControl implements ImageListener { private static final long serialVersionUID = -1510794248068777990L; private volatile ImageResponse imageResponse = new ImageResponse(); // private final UserAgentContext browserContext; private String lastSrc; public ImgControl(final HTMLImageElementImpl modelNode) { super(modelNode); // this.browserContext = pcontext; modelNode.addImageListener(this); } @Override public void paintComponent(final Graphics g) { super.paintComponent(g); final ImageResponse imageResponse = this.imageResponse; if (imageResponse.isDecoded()) { assert(imageResponse.img != null); final Image image = imageResponse.img; final Dimension size = this.getSize(); final Insets insets = this.getInsets(); final Graphics2D g2 = (Graphics2D) g; final int width = size.width - insets.left - insets.right; final int height = size.height - insets.top - insets.bottom; final int imgWidth = image.getWidth(this); final int imgHeight = image.getHeight(this); if (width < imgWidth || height < imgHeight) { // down-sampling needs better handling final Image scaledImg = getScaledInstance(image, width, height, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(scaledImg, insets.left, insets.top, width, height, this); } else { g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(image, insets.left, insets.top, width, height, this); } } else { // TODO: show alt text } } private Dimension preferredSize; private int declaredWidth; private int declaredHeight; @Override public void reset(final int availWidth, final int availHeight) { // Expected in the GUI thread. // final HTMLElementImpl element = this.controlElement; // TODO: Remove the parameters dw, dh, and members declaredWith, declaredHeight completely. // They seem to be used only for old style syntax. // final int dw = HtmlValues.getOldSyntaxPixelSize(element.getAttribute("width"), availWidth, -1); // final int dh = HtmlValues.getOldSyntaxPixelSize(element.getAttribute("height"), availHeight, -1); final int dw = -1; final int dh = -1; this.declaredWidth = dw; this.declaredHeight = dh; this.preferredSize = this.createPreferredSize(dw, dh); } @Override public VerticalAlign getVAlign() { final HTMLElementImpl element = this.controlElement; final @Nullable VerticalAlign verticalAlign = element.getRenderState().getVerticalAlign(); return verticalAlign == null ? VerticalAlign.BASELINE : verticalAlign; } @Override public Dimension getPreferredSize() { final Dimension ps = this.preferredSize; return ps == null ? new Dimension(0, 0) : ps; } public Dimension createPreferredSize(int dw, int dh) { final ImageResponse imageResponseLocal = this.imageResponse; if (!imageResponseLocal.isDecoded()) { return new Dimension(dw == -1 ? 0 : dw, dh == -1 ? 0 : dh); } assert(imageResponseLocal.img != null); final @NonNull Image img = imageResponseLocal.img; if (dw == -1) { if (dh != -1) { final int iw = HtmlValues.scaleToDevicePixels(img.getWidth(this)); final int ih = HtmlValues.scaleToDevicePixels(img.getHeight(this)); if (ih == 0) { dw = iw; } else { dw = (dh * iw) / ih; } } else { dw = HtmlValues.scaleToDevicePixels(img.getWidth(this)); } } if (dh == -1) { if (dw != -1) { final int iw = HtmlValues.scaleToDevicePixels(img.getWidth(this)); final int ih = HtmlValues.scaleToDevicePixels(img.getHeight(this)); if (iw == 0) { dh = ih == -1 ? 0 : ih; } else { dh = (dw * ih) / iw; } } else { dh = HtmlValues.scaleToDevicePixels(img.getHeight(this)); } } return new Dimension(dw, dh); } private final boolean checkPreferredSizeChange() { final Dimension newPs = this.createPreferredSize(this.declaredWidth, this.declaredHeight); final Dimension ps = this.preferredSize; if (ps == null) { return true; } if ((ps.width != newPs.width) || (ps.height != newPs.height)) { this.preferredSize = newPs; return true; } else { return false; } } /* * (non-Javadoc) * * @see java.awt.Component#imageUpdate(java.awt.Image, int, int, int, int, * int) */ @Override public boolean imageUpdate(final Image img, final int infoflags, final int x, final int y, final int w, final int h) { if (((infoflags & ImageObserver.ALLBITS) != 0) || ((infoflags & ImageObserver.FRAMEBITS) != 0)) { SwingUtilities.invokeLater(() -> { if (!checkPreferredSizeChange()) { repaint(); } else { ruicontrol.preferredSizeInvalidated(); } }); } return true; } /* * (non-Javadoc) * * @see java.awt.Component#imageUpdate(java.awt.Image, int, int, int, int, * int) */ public void imageUpdate(final Image img, final int w, final int h) { SwingUtilities.invokeLater(() -> { if (!checkPreferredSizeChange()) { repaint(); } else { ruicontrol.preferredSizeInvalidated(); } }); } public boolean paintSelection(final Graphics g, final boolean inSelection, final RenderableSpot startPoint, final RenderableSpot endPoint) { return inSelection; } public void imageLoaded(final ImageEvent event) { // Implementation of ImageListener. Invoked in a request thread most likely. final ImageResponse imageResponseLocal = event.imageResponse; this.imageResponse = imageResponseLocal; if (imageResponseLocal.isDecoded()) { assert(imageResponseLocal.img != null); final Image image = imageResponseLocal.img; final int width = image.getWidth(this); final int height = image.getHeight(this); this.imageUpdate(image, width, height); } } public void imageAborted() { // do nothing } @Override public String toString() { return "ImgControl[src=" + this.lastSrc + "]"; } // https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html // Adapted from: https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html /** * Convenience method that returns a scaled instance of the provided {@code BufferedImage}. * * @param img the original image to be scaled * @param targetWidth the desired width of the scaled instance, in pixels * @param targetHeight the desired height of the scaled instance, in pixels * @param hint one of the rendering hints that corresponds to {@code RenderingHints.KEY_INTERPOLATION} * @return a scaled version of the original {@code BufferedImage} */ private Image getScaledInstance(final Image img, final int targetWidth, final int targetHeight, final Object hint) { final int type = BufferedImage.TYPE_INT_ARGB; Image ret = img; int w = img.getWidth(this); int h = img.getHeight(this); while (w != targetWidth || h != targetHeight) { if (w > targetWidth) { w /= 2; } if (w < targetWidth) { w = targetWidth; } if (h > targetHeight) { h /= 2; } if (h < targetHeight) { h = targetHeight; } BufferedImage tmp = new BufferedImage(w, h, type); Graphics2D g2 = tmp.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); g2.drawImage(ret, 0, 0, w, h, null); g2.dispose(); ret = tmp; } return ret; } @Override public boolean isReadyToPaint() { return imageResponse.isReadyToPaint(); } }