/* * Artcodes recognises a different marker scheme that allows the * creation of aesthetically pleasing, even beautiful, codes. * Copyright (C) 2013-2016 The University of Nottingham * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.horizon.artcodes.detect; import android.graphics.Bitmap; import android.util.Log; import org.opencv.android.Utils; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.Point; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.imgproc.Imgproc; public class ImageBuffers { private byte[] buffer; /** cameraImage stores the full camera image in YUV (NV21, first 2/3 rows are Y data, final 1/3 rows is interleaved UV data) */ private Mat cameraImage; private Rect ROI; /** currentBuffer keeps track of which buffer contains the most recent result of processing */ private Mat currentBuffer; /** * <p>yuvBuffer contains the area of the image to process in YUV (if ROI.height==height of the * Y component this is a sub-Mat of cameraImage).</p> * <p>Note YUV data is arranged in two panes: <ul><li>a Y pane (that is the image intensity * [the same as cvtColor(BGR,GREY,COLOR_BGR2GRAY)]) one Y value per pixel, and</li><li>a VU * pane, one VU pair per 4 pixels.</li></ul></p> * <p> * YYyyYYYY<br> * YYyyYYYY<br> * YYYYYYYY<br> * YYYYYYYY<br> * VUvuVUVU<br> * VUVUVUVU * </p> * <p>Lower case letters show values that effect the same 4 pixels.</p> * */ private Mat yuvBuffer; /** greyBuffer contains the area of the image to process as a single channel grey image (this is a sub-Mat of yuvBuffer) */ private Mat greyBuffer; /** bgrBuffer contains the area of the image to process as a 3 channel Blue-Green-Red image (this is not a sub-Mat and is only generated in getBgrBuffer() ) */ private Mat bgrBuffer; private Mat overlay; private Bitmap overlayBitmap; private boolean overlayReady = false; private boolean detected = false; private boolean flip = false; private int rotations = 0; public void setImage(Mat image) { if (image==this.greyBuffer) { this.currentBuffer = greyBuffer; } else if (image==this.bgrBuffer) { this.currentBuffer = bgrBuffer; } else if (image==this.yuvBuffer) { this.currentBuffer = yuvBuffer; } else { Log.w(this.getClass().getSimpleName(), "setImage(Mat) called with buffer not provided by ImageBuffers."); } } public Mat getImageInAnyFormat() { if (currentBuffer==yuvBuffer || currentBuffer==greyBuffer) { return greyBuffer; } else if (currentBuffer==bgrBuffer) { return bgrBuffer; } else { Log.w(this.getClass().getSimpleName(), "In getImageInAnyFormat() 'currentBuffer' was not equal to any known buffer."); return null; } } public Mat getImageInGrey() { if (currentBuffer==yuvBuffer) { double[] rectArray = {0, 0, yuvBuffer.cols(), (yuvBuffer.rows()/3)*2}; greyBuffer = yuvBuffer.submat(new Rect(rectArray)); } else if (currentBuffer==bgrBuffer) { Imgproc.cvtColor(getBgrBuffer(), getGreyBuffer(), Imgproc.COLOR_BGR2GRAY); } else { getGreyBuffer(); } currentBuffer= greyBuffer; return greyBuffer; } public Mat getGreyBuffer() { if (greyBuffer==null) { if (yuvBuffer != null) { double[] rectArray = {0, 0, yuvBuffer.cols(), (yuvBuffer.rows()/3)*2}; greyBuffer = yuvBuffer.submat(new Rect(rectArray)); } else { Log.w(getClass().getSimpleName(), "Creating grey buffer in getGreyBuffer()"); greyBuffer = new Mat(ROI.height, ROI.width, CvType.CV_8UC1); } } return this.greyBuffer; } public Mat getImageInBgr() { if (currentBuffer==yuvBuffer) { Imgproc.cvtColor(yuvBuffer, getBgrBuffer(), Imgproc.COLOR_YUV2BGR_NV21); } else if (currentBuffer==greyBuffer) { Imgproc.cvtColor(greyBuffer, getBgrBuffer(), Imgproc.COLOR_GRAY2BGR); } currentBuffer = bgrBuffer; return bgrBuffer; } public Mat getBgrBuffer() { if (bgrBuffer==null) { bgrBuffer = new Mat(ROI.height, ROI.width, CvType.CV_8UC3); } return bgrBuffer; } public Mat getImageInYuv() { if (currentBuffer==greyBuffer) { Imgproc.cvtColor(greyBuffer, getBgrBuffer(), Imgproc.COLOR_GRAY2BGR); Imgproc.cvtColor(bgrBuffer, yuvBuffer, Imgproc.COLOR_BGR2YUV); } else if (currentBuffer==bgrBuffer) { Imgproc.cvtColor(bgrBuffer, yuvBuffer, Imgproc.COLOR_BGR2YUV); } currentBuffer = yuvBuffer; return yuvBuffer; } public void setImage(byte[] data) { overlayReady = false; cameraImage.put(0, 0, data); currentBuffer = yuvBuffer; } /** * Set the image data using a Bitmap. * Make sure the Bitmap is the right size for this ImageBuffers or if creating an ImageBuffers * just for this call createBuffer(bitmap.getWidth(), bitmap.getHeight(), 8) then setROI(null). * @param bitmap A Bitmap with format ARGB_8888 or RGB_565 (requirement from OpenCV). */ public void setImage(Bitmap bitmap) { overlayReady = false; // Utils.bitmapToMat creates a RGBA CV_8UC4 image. Mat rgbaImage = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4); Utils.bitmapToMat(bitmap, rgbaImage); // The 'bgrBuffer' is supposed to be BGR CV_8UC3, so convert. Mat bgrImage = this.getBgrBuffer(); Imgproc.cvtColor(rgbaImage, bgrImage, Imgproc.COLOR_RGBA2BGR); this.setImage(bgrImage); } public byte[] createBuffer(int imageWidth, int imageHeight, int imageDepth) { Log.i("ImageBuffer","createBuffer imageWidth: "+imageWidth + " imageHeight: "+imageHeight + " imageDepth: "+imageDepth); buffer = new byte[imageWidth * imageHeight * imageDepth / 8]; cameraImage = new Mat(imageHeight+imageHeight/2, imageWidth, CvType.CV_8UC1); Log.i("ImageBuffer", "cameraImage: "+cameraImage.cols()+"x"+cameraImage.rows()+"x"+cameraImage.channels()); return buffer; } public void setROI(Rect rect) { this.ROI = rect; if (rect == null) { double[] roiArray = {0, 0, cameraImage.cols(), (cameraImage.rows()/3)*2}; this.ROI = new Rect(roiArray); yuvBuffer = cameraImage; currentBuffer = yuvBuffer; } else { if (rect.height == (cameraImage.rows()/3)*2) { Log.i(this.getClass().getSimpleName(), "rect.height == camera image height (simple case)"); double[] rectArray = {rect.x, rect.y, rect.width, rect.height+rect.height/2}; yuvBuffer = cameraImage.submat(new Rect(rectArray)); } else { Log.i(this.getClass().getSimpleName(), "rect.height != camera image height (complicated case)"); // TODO throw new UnsupportedOperationException(); } } } public boolean hasDetected() { return detected; } public void setDetected(boolean detected) { this.detected = detected; } public Mat getOverlay() { return getOverlay(true); } public Bitmap createOverlayBitmap() { if (overlayReady) { if (overlayBitmap == null) { overlayBitmap = Bitmap.createBitmap(overlay.cols(), overlay.rows(), Bitmap.Config.ARGB_8888); } Utils.matToBitmap(overlay, overlayBitmap); return overlayBitmap; } else if (overlayBitmap != null) { overlayBitmap = null; } return null; } public void setRotation(int rotation) { this.rotations = rotation / 90; } public void setFrontFacing(boolean frontFacing) { this.flip = frontFacing; } public Mat getOverlay(boolean clear) { if (overlayReady) { return overlay; } if (currentBuffer==yuvBuffer) { this.setImage(this.getImageInGrey()); } rotate(currentBuffer); if (overlay == null) { overlay = new Mat(currentBuffer.rows(), currentBuffer.cols(), CvType.CV_8UC4); } if (clear) { overlay.setTo(new Scalar(0, 0, 0, 0)); } overlayReady = true; return overlay; } private void rotate(Mat image) { //0 : flip vertical; 1 flip horizontal int flip_horizontal_or_vertical = rotations > 0 ? 1 : 0; if (flip) { flip_horizontal_or_vertical = -1; } for (int i = 0; i != rotations; ++i) { Core.transpose(image, image); Core.flip(image, image, flip_horizontal_or_vertical); } } }