/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.heightMaps;
import org.pepsoft.util.IconUtils;
import org.pepsoft.util.MathUtils;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
/**
*
* @author SchmitzP
*/
public final class BitmapHeightMap extends AbstractHeightMap {
public BitmapHeightMap(String name, BufferedImage image, int channel, File imageFile, boolean repeat, boolean smoothScaling) {
super(name);
this.image = image;
raster = image.getRaster();
width = image.getWidth();
height = image.getHeight();
extent = repeat ? null : new Rectangle(0, 0, width, height);
bits = raster.getSampleModel().getSampleSize(0);
this.channel = channel;
this.imageFile = imageFile;
this.repeat = repeat;
this.smoothScaling = smoothScaling;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
raster = image.getRaster();
width = image.getWidth();
height = image.getHeight();
extent = repeat ? null : new Rectangle(0, 0, width, height);
bits = raster.getSampleModel().getSampleSize(0);
}
public int getChannel() {
return channel;
}
public void setChannel(int channel) {
this.channel = channel;
}
public void setImageFile(File imageFile) {
this.imageFile = imageFile;
}
public boolean isRepeat() {
return repeat;
}
public void setRepeat(boolean repeat) {
this.repeat = repeat;
extent = repeat ? null : new Rectangle(0, 0, width, height);
}
public boolean isSmoothScaling() {
return smoothScaling;
}
public void setSmoothScaling(boolean smoothScaling) {
this.smoothScaling = smoothScaling;
}
// HeightMap
@Override
public float getHeight(int x, int y) {
if (repeat) {
return raster.getSample(MathUtils.mod(x, width), MathUtils.mod(y, height), channel);
} else if (extent.contains(x, y)) {
return raster.getSample(x, y, channel);
} else {
return 0f;
}
}
@Override
public float getHeight(float x, float y) {
if (! smoothScaling) {
return getHeight((int) x, (int) y);
} else {
// Bicubic interpolation
x -= Math.signum(x) / 2;
y -= Math.signum(y) / 2;
int xFloor = (int) Math.floor(x), yFloor = (int) Math.floor(y);
float xDelta = x - xFloor, yDelta = y - yFloor;
float val1 = cubicInterpolate(getExtHeight(xFloor - 1, yFloor - 1), getExtHeight(xFloor - 1, yFloor), getExtHeight(xFloor - 1, yFloor + 1), getExtHeight(xFloor - 1, yFloor + 2), yDelta);
float val2 = cubicInterpolate(getExtHeight(xFloor, yFloor - 1), getExtHeight(xFloor, yFloor), getExtHeight(xFloor, yFloor + 1), getExtHeight(xFloor, yFloor + 2), yDelta);
float val3 = cubicInterpolate(getExtHeight(xFloor + 1, yFloor - 1), getExtHeight(xFloor + 1, yFloor), getExtHeight(xFloor + 1, yFloor + 1), getExtHeight(xFloor + 1, yFloor + 2), yDelta);
float val4 = cubicInterpolate(getExtHeight(xFloor + 2, yFloor - 1), getExtHeight(xFloor + 2, yFloor), getExtHeight(xFloor + 2, yFloor + 1), getExtHeight(xFloor + 2, yFloor + 2), yDelta);
return cubicInterpolate(val1, val2, val3, val4, xDelta);
}
}
/**
* Private version of {@link #getHeight(float, float)}} which extends the
* edge pixels of the image if it is non-repeating, to make the bicubic
* interpolation work correctly around the edges.
*/
private float getExtHeight(int x, int y) {
if (repeat) {
return raster.getSample(MathUtils.mod(x, width), MathUtils.mod(y, height), channel);
} else if (extent.contains(x, y)) {
return raster.getSample(x, y, channel);
} else if (x < 0) {
// West of the extent
if (y < 0) {
// Northwest of the extent
return raster.getSample(0, 0, channel);
} else if (y < height) {
// Due west of the extent
return raster.getSample(0, y, channel);
} else {
// Southwest of the extent
return raster.getSample(0, height - 1, channel);
}
} else if (x < width) {
// North or south of the extent
if (y < 0) {
// Due north of the extent
return raster.getSample(x, 0, channel);
} else {
// Due south of the extent
return raster.getSample(x, height - 1, channel);
}
} else {
// East of the extent
if (y < 0) {
// Northeast of the extent
return raster.getSample(width - 1, 0, channel);
} else if (y < height) {
// Due east of the extent
return raster.getSample(width - 1, y, channel);
} else {
// Southeast of the extent
return raster.getSample(width - 1, height - 1, channel);
}
}
}
@Override
public Rectangle getExtent() {
return extent;
}
@Override
public int getColour(int x, int y) {
if (repeat) {
return image.getRGB(MathUtils.mod(x, width), MathUtils.mod(y, height));
} else if (extent.contains(x, y)) {
return image.getRGB(x, y);
} else {
return 0;
}
}
@Override
public Icon getIcon() {
return ICON_BITMAP_HEIGHTMAP;
}
@Override
public float[] getRange() {
switch (bits) {
case 8:
return RANGE_8BIT;
case 16:
return RANGE_16BIT;
default:
throw new UnsupportedOperationException();
}
}
public File getImageFile() {
return imageFile;
}
public static BitmapHeightMapBuilder build() {
return new BitmapHeightMapBuilder();
}
/**
* Cubic interpolation using Catmull-Rom splines.
*/
private float cubicInterpolate(float y0, float y1, float y2, float y3, float mu) {
// float a0 = -0.5f * y0 + 1.5f * y1 - 1.5f * y2 + 0.5f * y3;
// float a1 = y0 - 2.5f * y1 + 2 * y2 - 0.5f * y3;
// float a2 = -0.5f * y0 + 0.5f * y2;
// float mu2 = mu * mu;
// return a0 * mu * mu2 + a1 * mu2 + a2 * mu + y1;
return y1 + 0.5f * mu * (y2 - y0 + mu * (2.0f * y0 - 5.0f * y1 + 4.0f * y2 - y3 + mu * (3.0f * (y1 - y2) + y3 - y0)));
}
private BufferedImage image;
private int channel, width, height, bits;
private Raster raster;
private Rectangle extent;
private File imageFile;
private boolean repeat, smoothScaling;
private static final long serialVersionUID = 1L;
private static final Icon ICON_BITMAP_HEIGHTMAP = IconUtils.loadScaledIcon("org/pepsoft/worldpainter/icons/height_map.png");
private static final float[] RANGE_8BIT = {0.0f, 255.0f}, RANGE_16BIT = {0.0f, 65535.0f};
public static class BitmapHeightMapBuilder {
public BitmapHeightMapBuilder withName(String name) {
this.name = name;
return this;
}
public BitmapHeightMapBuilder withChannel(int channel) {
this.channel = channel;
return this;
}
public BitmapHeightMapBuilder withImage(BufferedImage image) {
this.image = image;
return this;
}
public BitmapHeightMapBuilder withFile(File file) {
this.file = file;
return this;
}
public BitmapHeightMapBuilder withRepeat(boolean repeat) {
this.repeat = repeat;
return this;
}
public BitmapHeightMapBuilder withSmoothScaling(boolean smoothScaling) {
this.smoothScaling = smoothScaling;
return this;
}
public BitmapHeightMap now() {
return new BitmapHeightMap(name, image, channel, file, repeat, smoothScaling);
}
private String name;
private BufferedImage image;
private int channel;
private File file;
private boolean repeat, smoothScaling;
}
}