package jp.crwdev.app.imagefilter; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import com.mortennobel.imagescaling.ImageUtils; public class GrayscaleOp implements BufferedImageOp { final double Rparam = 0.298912 * 1024; final double Gparam = 0.586611 * 1024; final double Bparam = 0.114478 * 1024; @Override public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel arg1) { ColorModel model = src.getColorModel(); return new BufferedImage(model, model.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), model.isAlphaPremultiplied(), null); } @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { Dimension dstDimension = new Dimension(src.getWidth(),src.getHeight()); int dstWidth = dstDimension.width; int dstHeight = dstDimension.height; BufferedImage bufferedImage = doFilter(src, dest, dstWidth, dstHeight); return bufferedImage; } private BufferedImage doFilter(BufferedImage srcImg, BufferedImage dest, int dstWidth, int dstHeight){ final int numberOfThreads = 4; if( srcImg.getType() == BufferedImage.TYPE_BYTE_BINARY || srcImg.getType() == BufferedImage.TYPE_BYTE_INDEXED || srcImg.getType() == BufferedImage.TYPE_CUSTOM){ srcImg = ImageUtils.convert(srcImg, srcImg.getColorModel().hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); } int nrChannels = ImageUtils.nrChannels(srcImg); @SuppressWarnings("unused") int srcWidth = srcImg.getWidth(); int srcHeight = srcImg.getHeight(); byte[][] workPixels = new byte[srcHeight][dstWidth*nrChannels]; final int nrChannelsCopy = nrChannels; final BufferedImage srcImgCopy = srcImg; final byte[][] workPixelsCopy = workPixels; Thread[] threads = new Thread[numberOfThreads-1]; for (int i=1;i<numberOfThreads;i++){ final int finalI = i; threads[i-1] = new Thread(new Runnable(){ public void run(){ horizontallyFromSrcToWork(srcImgCopy, workPixelsCopy, finalI, numberOfThreads, nrChannelsCopy); } }); threads[i-1].start(); } horizontallyFromSrcToWork(srcImgCopy, workPixelsCopy,0,numberOfThreads, nrChannelsCopy); waitForAllThreads(threads); byte[] outPixels = new byte[dstWidth*dstHeight*nrChannels]; BufferedImage out; if (dest!=null && dstWidth==dest.getWidth() && dstHeight==dest.getHeight()){ out = dest; int nrDestChannels = ImageUtils.nrChannels(dest); if (nrDestChannels != nrChannels){ throw new RuntimeException("Destination image must be compatible width source image."); } } else{ out = new BufferedImage(dstWidth, dstHeight, getResultBufferedImageType(nrChannels, srcImg)); } for(int y=0; y<dstHeight; y++){ int dh = y * dstWidth * nrChannels; for(int x=0; x<dstWidth; x++){ int dx = x * nrChannels; for(int n=0; n<nrChannels; n++){ outPixels[dh + dx + n] = workPixels[y][dx + n]; } } } ImageUtils.setBGRPixels(outPixels, out, 0, 0, dstWidth, dstHeight); return out; } private void horizontallyFromSrcToWork(BufferedImage srcImg, byte[][] workPixels, int start, int delta, int nrChannels) { int srcWidth = srcImg.getWidth(); int srcHeight = srcImg.getHeight(); final int[] tempPixels = new int[srcWidth]; // Used if we work on int based bitmaps, later used to keep channel values final byte[] srcPixels = new byte[srcWidth*nrChannels]; // create reusable row to minimize memory overhead for (int k = start; k < srcHeight; k=k+delta){ ImageUtils.getPixelsBGR(srcImg, k, srcWidth, srcPixels, tempPixels); for(int x=0; x<srcWidth; x++){ int i = x * nrChannels; int n = 0; if(nrChannels == 1){ n = srcPixels[i]; } else{ byte b = srcPixels[i + 0]; byte g = srcPixels[i + 1]; byte r = srcPixels[i + 2]; n = (((int)( r*Rparam + g*Gparam + b*Bparam )) >> 10); } for(int c=0; c<nrChannels; c++){ workPixels[k][i + c] = (byte)(n&0xff); } } } } protected int getResultBufferedImageType(int nrChannels, BufferedImage srcImg) { return nrChannels == 3 ? BufferedImage.TYPE_3BYTE_BGR : (nrChannels == 4 ? BufferedImage.TYPE_4BYTE_ABGR : (srcImg.getSampleModel().getDataType() == DataBuffer.TYPE_USHORT ? BufferedImage.TYPE_USHORT_GRAY : BufferedImage.TYPE_BYTE_GRAY)); } private void waitForAllThreads(Thread[] threads) { try { for (Thread t:threads){ t.join(Long.MAX_VALUE); } } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override public Rectangle2D getBounds2D(BufferedImage src) { return new Rectangle(0, 0, src.getWidth(), src.getHeight()); } @Override public Point2D getPoint2D(Point2D src, Point2D dst) { return (Point2D) src.clone(); } @Override public RenderingHints getRenderingHints() { return null; } }