Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.makelangelo.makeart;
import com.marginallyclever.makelangelo.makeart.imagefilter.ImageFilter;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.util.Arrays;
/**
* TransformedImage is a {@link BufferedImage}, with a transformation matrix on top.
* All sampling interactions are done in paper space coordinates and {@link TransformedImage} takes care of the rest.
* The original {@link BufferedImage} is not modified so there is no data loss. This also means one matrix transform
* per pixel sample, which is slow.
* @author Dan Royer
*/
public class TransformedImage {
private final BufferedImage sourceImage;
private float scaleX, scaleY;
private float translateX, translateY;
public TransformedImage(BufferedImage src) {
sourceImage = src;
translateX = -src.getWidth() / 2.0f;
translateY = -src.getHeight() / 2.0f;
scaleX = 1;
scaleY = -1;
}
// https://stackoverflow.com/questions/3514158/how-do-you-clone-a-bufferedimage
protected BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(null);
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
public TransformedImage(TransformedImage copy) {
sourceImage = deepCopy(copy.sourceImage);
translateX = copy.translateX;
translateY = copy.translateY;
scaleX = copy.scaleX;
scaleY = copy.scaleY;
}
public boolean canSampleAt(double x, double y) {
int sampleX = getTransformedX(x);
int sampleY = getTransformedY(y);
if (sampleX < 0 || sampleX >= sourceImage.getWidth ()) return false;
if (sampleY < 0 || sampleY >= sourceImage.getHeight()) return false;
return true;
}
public void copySettingsFrom(TransformedImage other) {
scaleX = other.scaleX;
scaleY = other.scaleY;
translateX = other.translateX;
translateY = other.translateY;
}
public float getScaleX() {
return scaleX;
}
public float getScaleY() {
return scaleY;
}
public BufferedImage getSourceImage() {
return sourceImage;
}
private int getTransformedX(double x) {
return (int) ((x / scaleX) - translateX);
}
public int getTransformedY(double y) {
return (int) ((y / scaleY) - translateY);
}
/**
* Returns the greyscale intensity [0...255]
* @param cx center of the sample area
* @param cy center of the sample area
* @param radius radius of the sample area
* @return the greyscale intensity [0...255]
*/
public int sample(double cx, double cy, double radius) {
return sample(cx-radius,cy-radius,cx+radius,cy+radius);
}
/**
* Sample the image, taking into account fractions of pixels. left must be less than right, bottom must be less than top.
* @param x0 left
* @param y0 top
* @param x1 right
* @param y1 bottom
* @return greyscale intensity in this region. [0...255]v
*/
public int sample(double x0, double y0, double x1, double y1) {
double sampleValue = 0;
double weightedSum = 0;
int left = (int)Math.floor(x0);
int right = (int)Math.ceil (x1);
int bottom = (int)Math.floor(y0);
int top = (int)Math.ceil (y1);
// calculate the weight matrix
int w = Math.max(1,right-left);
int h = Math.max(1,top-bottom);
if(w==1 && h==1) {
if (canSampleAt(left, bottom)) {
return sample1x1Unchecked(left, bottom);
} else {
return 0;
}
}
double [] m = new double[w*h];
Arrays.fill(m, 1);
// bottom edge
if(bottom<y0) {
double yWeightStart = y0-bottom;
for(int i=0;i<w;++i) {
m[i]*=yWeightStart;
}
}
// top edge
if(top>y1) {
double yWeightEnd = top-y1;
for(int i=0;i<w;++i) {
m[m.length-w+i]*=yWeightEnd;
}
}
// left edge
if(left<x0) {
double xWeightStart = x0-left;
for(int i=0;i<h;++i) {
m[i*w]*=xWeightStart;
}
}
// right edge
if(right>x1) {
double xWeightEnd = right-x1;
for(int i=0;i<h;++i) {
m[(i+1)*w-1]*=xWeightEnd;
}
}
int i=0;
for(int y=bottom;y<top;++y) {
for(int x=left;x<right;++x) {
double s = m[i++];
if (canSampleAt(x, y)) {
sampleValue += sample1x1Unchecked(x,y) * s;
weightedSum += s;
}
}
}
if (weightedSum == 0)
return 255;
double result = sampleValue / weightedSum;
return (int)Math.min( Math.max(result, 0), 255 );
}
/**
* Attempt to sample a pixel of the source image, if the (x,y) coordinate is within the bounds of the
* @param x paper-space coordinates of the image
* @param y paper-space coordinates of the image
* @return 255 if the image cannot be sampled. The intensity of the color channel [0...255]
*/
public int sample1x1(double x, double y) {
if (canSampleAt(x, y)) {
return sample1x1Unchecked(x, y);
}
return 255;
}
/**
* Sample a pixel of the source image without a safety check.
* @param x paper-space coordinates of the image
* @param y paper-space coordinates of the image
* @return 255 if the image cannot be sampled. The intensity of the color channel [0...255]. the color channel is selected with
*/
public int sample1x1Unchecked(double x, double y) {
int sampleX = getTransformedX(x);
int sampleY = getTransformedY(y);
int c2 = sourceImage.getRGB(sampleX, sampleY);
return ImageFilter.decode32bit(c2) & 0xFF;
}
// sample the pixels from x0,y0 (top left) to x1,y1 (bottom right)
public int sampleArea(int x0, int y0, int x1, int y1) {
int value = 0;
int sum = 0;
for (int y = y0; y < y1; ++y) {
for (int x = x0; x < x1; ++x) {
if (canSampleAt(x, y)) {
value += sample1x1Unchecked(x, y);
++sum;
}
}
}
if (sum == 0)
return 255;
return value / sum;
}
public void setScale(float x,float y) {
scaleX = x;
scaleY = y;
}
public void setRGB(float x, float y, int c) {
sourceImage.setRGB(getTransformedX(x), getTransformedY(y), c);
}
}
112549ae4This file is pinned to the selected release. Download the original to open it in your design tool.