package org.svu;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class EdgeDetector {
private final static int BORDER_WIDTH = 1;
private final static int NCOLORS = 3; // R G B
private static final int[] H = {-1, 0, 1, -2, 0, 2, -1, 0, 1};
private static final int[] V = {-1, -2, -1, 0, 0, 0, 1, 2, 1};
private int[] newPixels;
private final File in;
private final File out;
private final CyclicBarrier barrier;
public EdgeDetector(final String inName, final String outName, final int num) {
this.in = new File(inName).getAbsoluteFile();
this.out = new File(outName).getAbsoluteFile();
this.barrier = new CyclicBarrier(num + 1);
int numCores = Runtime.getRuntime().availableProcessors();
if (num != numCores) {
System.out.println(String.format(
"WARNING: Thread count is not optimal, specified %d but can run %d", num, numCores
));
}
}
public void detect() throws IOException, BrokenBarrierException, InterruptedException {
final BufferedImage origImg = ImageIO.read(in);
final BufferedImage newImg = process(origImg);
ImageIO.write(newImg, "PNG", out);
}
public BufferedImage process(BufferedImage image) throws BrokenBarrierException, InterruptedException {
final int width = image.getWidth();
final int height = image.getHeight();
newPixels = new int[width * height];
int parts = barrier.getParties() - 1;
int step = height / parts;
int high = BORDER_WIDTH, low = step;
for (int i = 0; i < parts; i++) {
if (low > (height - BORDER_WIDTH)) {
low = height - BORDER_WIDTH;
}
new Thread(new Processor(image, high, low)).start();
high = low;
low += step;
}
barrier.await();
final BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
newImage.setRGB(0, 0, width, height, newPixels, 0, width);
return newImage;
}
public static void main(final String[] args) throws Exception {
final String in = args[0];
final String out = args[1];
final int times = Integer.parseInt(args[2]);
final int numThreads = Integer.parseInt(args[3]);
System.out.println(String.format("Converting from %s to %s, repeat %s times, using %s threads", args));
final EdgeDetector ed = new EdgeDetector(in, out, numThreads);
System.out.println("Warming up...");
for (int i = times * 100; i-- > 0;) {
ed.detect();
System.out.print(" " + (100 - i / times) + "%\r");
}
System.out.print("Measure...");
long time = System.nanoTime();
for (int i = times; i-- > 0;) {
ed.detect();
}
time = System.nanoTime() - time;
System.out.println(String.format(
"Done.\nElapsed %d ms (%.3f ms per each)",
TimeUnit.NANOSECONDS.toMillis(time),
(double) TimeUnit.NANOSECONDS.toMillis(time) / times
));
}
public class Processor implements Runnable {
private int high, low;
private BufferedImage image;
public Processor(BufferedImage image, int high, int low) {
this.image = image;
this.high = high;
this.low = low;
}
public void run() {
final int width = image.getWidth();
int newPixelsOffset = BORDER_WIDTH + (width * high);
// [0]:B [1]:G [2]:R
final int[] colorValuesH = new int[NCOLORS];
final int[] colorValuesV = new int[NCOLORS];
final int sampleWidth = 1 + (BORDER_WIDTH << 1);
final int sampleSize = sampleWidth * sampleWidth;
final int[] samplePixels = new int[sampleSize];
for (int y = high; y < low; y++, newPixelsOffset += (BORDER_WIDTH << 1)) {
for (int x = BORDER_WIDTH; x < width - BORDER_WIDTH; x++) {
// TYPE_INT_ARGB
image.getRGB(x - BORDER_WIDTH, y - BORDER_WIDTH, sampleWidth, sampleWidth, samplePixels, 0, sampleWidth);
Arrays.fill(colorValuesH, 0);
Arrays.fill(colorValuesV, 0);
for (int o = 0; o < sampleSize; o++) {
int pixel = samplePixels[o];
// Process bytes in B, G, R order
for (int c = 0; c < NCOLORS; c++) {
final int val = pixel & 0xFF;
colorValuesH[c] += val * H[o];
colorValuesV[c] += val * V[o];
pixel >>= 8;
}
}
int pixel = 0;
// R, then G, then B
for (int c = NCOLORS; --c >= 0;) {
final int h = saturate(colorValuesH[c]);
final int v = saturate(colorValuesV[c]);
pixel <<= 8;
pixel |= (h > v)? h: v;
}
newPixels[newPixelsOffset++] = pixel;
}
}
try {
barrier.await();
} catch (Exception e) {
// ignore
}
}
private int saturate(int val) {
return (val < 0)? 0: (val >= 0x100)? 0xFF: val;
}
}
}