/*
* $Id$
*
* Copyright (c) 2007-2010 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.imageop;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang.builder.HashCodeBuilder;
import VASSAL.tools.image.GeneralFilter;
import VASSAL.tools.image.ImageUtils;
/**
* An {@link ImageOp} which scales its source.
*
* @since 3.1.0
* @author Joel Uckelman
*/
public class ScaleOpBitmapImpl extends AbstractTiledOpImpl
implements ScaleOp {
protected final ImageOp sop;
protected final double scale;
protected final RenderingHints hints;
protected final int hash;
// FIXME: We try to always use the same hints object because hints is
// used in our equals() and RenderingHints.equals() is ridiculously slow
// if a full comparison is made. This way hints == defaultHints, usually,
// and so a quick equality comparison succeeds.
protected static final RenderingHints defaultHints =
ImageUtils.getDefaultHints();
/**
* Constructs an <code>ImageOp</code> which will scale
* the image produced by its source <code>ImageOp</code>.
*
* @param sop the source operation
* @param scale the scale factor
*/
public ScaleOpBitmapImpl(ImageOp sop, double scale) {
this(sop, scale, defaultHints);
}
/**
* Constructs an <code>ImageOp</code> which will scale
* the image produced by its source <code>ImageOp</code>.
*
* @param sop the source operation
* @param scale the scale factor
* @param hints rendering hints
*/
public ScaleOpBitmapImpl(ImageOp sop, double scale, RenderingHints hints) {
if (sop == null)
throw new IllegalArgumentException("Attempt to scale null image");
if (scale <= 0)
throw new IllegalArgumentException("Cannot scale image at " + scale);
this.sop = sop;
this.scale = scale;
this.hints = hints;
hash = new HashCodeBuilder().append(sop)
.append(scale)
.append(hints)
.toHashCode();
}
public List<VASSAL.tools.opcache.Op<?>> getSources() {
return Collections.<VASSAL.tools.opcache.Op<?>>singletonList(sop);
}
/**
* {@inheritDoc}
*
* @throws Exception passed up from the source <code>ImageOp</code>.
*/
public BufferedImage eval() throws Exception {
return ImageUtils.transform(sop.getImage(null), scale, 0.0, hints);
}
/** {@inheritDoc} */
protected void fixSize() {
if ((size = getSizeFromCache()) == null) {
size = ImageUtils.transform(
new Rectangle(sop.getSize()), scale, 0.0).getSize();
}
}
protected ImageOp createTileOp(int tileX, int tileY) {
return new TileOp(this, tileX, tileY);
}
private static class TileOp extends AbstractTileOpImpl {
private final ImageOp sop;
private final int dx0, dy0, dw, dh;
private final double scale;
@SuppressWarnings("unused")
private final RenderingHints hints;
private final int hash;
private static final GeneralFilter.Filter downFilter =
new GeneralFilter.Lanczos3Filter();
private static final GeneralFilter.Filter upFilter =
new GeneralFilter.MitchellFilter();
public TileOp(ScaleOpBitmapImpl rop, int tileX, int tileY) {
if (rop == null) throw new IllegalArgumentException();
if (tileX < 0 || tileX >= rop.getNumXTiles() ||
tileY < 0 || tileY >= rop.getNumYTiles())
throw new IndexOutOfBoundsException();
sop = rop.sop;
scale = rop.getScale();
hints = rop.getHints();
final Rectangle sr =
new Rectangle(0, 0,
(int)(sop.getWidth()*scale),
(int)(sop.getHeight()*scale));
dx0 = tileX*rop.getTileWidth();
dy0 = tileY*rop.getTileHeight();
dw = Math.min(rop.getTileWidth(), sr.width - dx0);
dh = Math.min(rop.getTileHeight(), sr.height - dy0);
size = new Dimension(dw,dh);
hash = new HashCodeBuilder().append(sop)
.append(dx0)
.append(dy0)
.append(dw)
.append(dh)
.toHashCode();
}
public List<VASSAL.tools.opcache.Op<?>> getSources() {
return Collections.<VASSAL.tools.opcache.Op<?>>singletonList(sop);
}
public BufferedImage eval() throws Exception {
if (dw < 1 || dh < 1) return ImageUtils.NULL_IMAGE;
// ensure that src is a type which GeneralFilter can handle
final BufferedImage src = ImageUtils.coerceToIntType(sop.getImage(null));
final Rectangle sr =
new Rectangle(0, 0,
(int)(sop.getWidth()*scale),
(int)(sop.getHeight()*scale));
final WritableRaster dstR = src.getColorModel()
.createCompatibleWritableRaster(dw, dh)
.createWritableTranslatedChild(dx0, dy0);
// zoom! zoom!
GeneralFilter.zoom(dstR, sr, src, scale < 1.0f ? downFilter : upFilter);
return ImageUtils.toCompatibleImage(new BufferedImage(
src.getColorModel(),
dstR.createWritableTranslatedChild(0,0),
src.isAlphaPremultiplied(),
null
));
}
protected void fixSize() { }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || o.getClass() != this.getClass()) return false;
final TileOp op = (TileOp) o;
return dx0 == op.dx0 &&
dy0 == op.dy0 &&
dw == op.dw &&
dh == op.dh &&
scale == op.scale &&
sop.equals(op.sop);
}
@Override
public int hashCode() {
return hash;
}
/** {@inheritDoc} */
@Override
public String toString() {
return getClass().getName() +
"[sop=" + sop + ",scale=" + scale +
",dx0=" + dx0 + ",dy0=" + dy0 + ",dw=" + dw + ",dy=" + dh + "]";
}
}
public RenderingHints getHints() {
return hints;
}
/**
* Returns the scale factor.
*
* @return the scale factor, in the range <code>(0,Double.MAX_VALUE]</code>.
*/
public double getScale() {
return scale;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || o.getClass() != this.getClass()) return false;
final ScaleOpBitmapImpl op = (ScaleOpBitmapImpl) o;
return scale == op.scale &&
sop.equals(op.sop) &&
hints.equals(op.hints);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return hash;
}
/** {@inheritDoc} */
@Override
public String toString() {
return getClass().getName() +
"[sop=" + sop + ",scale=" + scale + ",hints=" + hints + "]";
}
}