/*
* Encog(tm) Core v3.3 - Java Version
* http://www.heatonresearch.com/encog/
* https://github.com/encog/encog-java-core
* Copyright 2008-2014 Heaton Research, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* For more information on Heaton Research copyrights, licenses
* and trademarks visit:
* http://www.heatonresearch.com/copyright
*/
package org.encog.util.downsample;
import java.awt.Image;
import java.awt.image.PixelGrabber;
import org.encog.EncogError;
import org.encog.util.ImageSize;
/**
* Downsample an image keeping the RGB colors.
*
*/
public class RGBDownsample implements Downsample {
/**
* The pixel map from the image.
*/
private int[] pixelMap;
/**
* The downsample x-ratio.
*/
private double ratioX;
/**
* The downsample y-ratio.
*/
private double ratioY;
/**
* The image height.
*/
private int imageHeight;
/**
* The image width.
*/
private int imageWidth;
/**
* The left boundary of the downsample.
*/
private int downSampleLeft;
/**
* The right boundary of the downsample.
*/
private int downSampleRight;
/**
* The top boundary of the downsample.
*/
private int downSampleTop;
/**
* The bottom boundary of the downsample.
*/
private int downSampleBottom;
/**
* The current red average.
*/
private int currentRed;
/**
* The current blue average.
*/
private int currentBlue;
/**
* The current green average.
*/
private int currentGreen;
/**
* Called to downsample the image and store it in the down sample component.
*
* @param image
* The image to downsample.
* @param height
* The height to downsample to.
* @param width
* THe width to downsample to.
* @return The downsampled image.
*/
public double[] downSample(final Image image, final int height,
final int width) {
processImage(image);
final double[] result = new double[height * width * 3];
final PixelGrabber grabber = new PixelGrabber(image, 0, 0,
this.imageWidth, this.imageHeight, true);
try {
grabber.grabPixels();
} catch (final InterruptedException e) {
throw new EncogError(e);
}
this.pixelMap = (int[]) grabber.getPixels();
// now downsample
this.ratioX = (double) (this.downSampleRight - this.downSampleLeft)
/ (double) width;
this.ratioY = (double) (this.downSampleBottom - this.downSampleTop)
/ (double) height;
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
downSampleRegion(x, y);
result[index++] = this.currentRed;
result[index++] = this.currentGreen;
result[index++] = this.currentBlue;
}
}
return result;
}
/**
* Called to downsample a region of the image.
*
* @param x
* The x coordinate of the resulting downsample.
* @param y
* The y coordinate of the resulting downsample.
*/
public void downSampleRegion(final int x, final int y) {
final int startX = (int) (this.downSampleLeft + x * this.ratioX);
final int startY = (int) (this.downSampleTop + y * this.ratioY);
int endX = (int) (startX + this.ratioX);
int endY = (int) (startY + this.ratioY);
endX = Math.min(this.imageWidth, endX);
endY = Math.min(this.imageHeight, endY);
// sample at least one pixel (for instance if down-sample size is the same as regular size)
endX = Math.max(startX+1, endX);
endY = Math.max(startY+1, endY);
int redTotal = 0;
int greenTotal = 0;
int blueTotal = 0;
int total = 0;
for (int yy = startY; yy < endY; yy++) {
for (int xx = startX; xx < endX; xx++) {
final int loc = xx + yy * this.imageWidth;
final int pixel = this.pixelMap[loc];
final int red = pixel >> 16 & 0xff;
final int green = pixel >> 8 & 0xff;
final int blue = pixel & 0xff;
redTotal += red;
greenTotal += green;
blueTotal += blue;
total++;
}
}
this.currentRed = redTotal / total;
this.currentGreen = greenTotal / total;
this.currentBlue = blueTotal / total;
}
/**
* This method is called to automatically crop the image so that whitespace
* is removed.
*/
public void findBounds() {
// top line
for (int y = 0; y < this.imageHeight; y++) {
if (!hLineClear(y)) {
this.downSampleTop = y;
break;
}
}
// bottom line
for (int y = this.imageHeight - 1; y >= 0; y--) {
if (!hLineClear(y)) {
this.downSampleBottom = y;
break;
}
}
// left line
for (int x = 0; x < this.imageWidth; x++) {
if (!vLineClear(x)) {
this.downSampleLeft = x;
break;
}
}
// right line
for (int x = this.imageWidth - 1; x >= 0; x--) {
if (!vLineClear(x)) {
this.downSampleRight = x;
break;
}
}
}
public int getCurrentBlue() {
return this.currentBlue;
}
public int getCurrentGreen() {
return this.currentGreen;
}
public int getCurrentRed() {
return this.currentRed;
}
/**
* @return the downSampleBottom
*/
public int getDownSampleBottom() {
return this.downSampleBottom;
}
/**
* @return the downSampleLeft
*/
public int getDownSampleLeft() {
return this.downSampleLeft;
}
/**
* @return the downSampleRight
*/
public int getDownSampleRight() {
return this.downSampleRight;
}
/**
* @return the downSampleTop
*/
public int getDownSampleTop() {
return this.downSampleTop;
}
/**
* @return the imageHeight
*/
public int getImageHeight() {
return this.imageHeight;
}
/**
* @return the imageWidth
*/
public int getImageWidth() {
return this.imageWidth;
}
/**
* @return the pixelMap
*/
public int[] getPixelMap() {
return this.pixelMap;
}
/**
* @return the ratioX
*/
public double getRatioX() {
return this.ratioX;
}
/**
* @return the ratioY
*/
public double getRatioY() {
return this.ratioY;
}
/**
* This method is called internally to see if there are any pixels in the
* given scan line. This method is used to perform autocropping.
*
* @param y
* The horizontal line to scan.
* @return True if there were any pixels in this horizontal line.
*/
private boolean hLineClear(final int y) {
for (int i = 0; i < this.imageWidth; i++) {
if (this.pixelMap[y * this.imageWidth + i] != -1) {
return false;
}
}
return true;
}
/**
* Process the image and prepare it to be downsampled.
*
* @param image
* The image to downsample.
*/
public void processImage(final Image image) {
final ImageSize size = new ImageSize(image);
this.imageHeight = size.getHeight();
this.imageWidth = size.getWidth();
this.downSampleLeft = 0;
this.downSampleTop = 0;
this.downSampleRight = this.imageWidth;
this.downSampleBottom = this.imageHeight;
this.ratioX = (double) (this.downSampleRight - this.downSampleLeft)
/ (double) getImageWidth();
this.ratioY = (double) (this.downSampleBottom - this.downSampleTop)
/ (double) getImageHeight();
}
/**
* Set the current blue average.
* @param currentBlue The current blue average.
*/
public void setCurrentBlue(final int currentBlue) {
this.currentBlue = currentBlue;
}
/**
* Set the current green average.
* @param currentGreen The current green average.
*/
public void setCurrentGreen(final int currentGreen) {
this.currentGreen = currentGreen;
}
/**
* Set the current red average.
* @param currentRed The current red average.
*/
public void setCurrentRed(final int currentRed) {
this.currentRed = currentRed;
}
/**
* Set the pixel map.
* @param pixelMap The pixel map.
*/
public void setPixelMap(final int[] pixelMap) {
this.pixelMap = pixelMap;
}
/**
* This method is called to determine ....
*
* @param x
* The vertical line to scan.
* @return True if there are any pixels in the specified vertical line.
*/
private boolean vLineClear(final int x) {
for (int i = 0; i < this.imageHeight; i++) {
if (this.pixelMap[i * this.imageWidth + x] != -1) {
return false;
}
}
return true;
}
}