All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jna.platform.RasterRangesUtils Maven / Gradle / Ivy

There is a newer version: 5.15.0
Show newest version
/* Copyright (c) 2007 Olivier Chafik, All Rights Reserved
 * Copyright (c) 2008 Timothy Wall, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2
 * alternative Open Source/Free licenses: LGPL 2.1 or later and
 * Apache License 2.0. (starting with JNA version 4.0.0).
 *
 * You can freely decide which license you want to apply to
 * the project.
 *
 * You may obtain a copy of the LGPL License at:
 *
 * http://www.gnu.org/licenses/licenses.html
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 *
 * You may obtain a copy of the Apache License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.platform;

import java.awt.Rectangle;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * Methods that are useful to decompose a raster into a set of rectangles.
 * An occupied pixel has two possible meanings, depending on the raster :
 * 
    *
  • if the raster has an alpha layer, occupied means with alpha not null
  • *
  • if the raster doesn't have any alpha layer, occupied means not completely black
  • *
* @author Olivier Chafik */ public class RasterRangesUtils { /// Masks used to isolate the current column in a set of 8 binary columns packed in a byte private static final int[] subColMasks = new int[] { 0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001 }; private static final Comparator COMPARATOR = new Comparator() { @Override public int compare(Object o1, Object o2) { return ((Rectangle)o1).x - ((Rectangle)o2).x; } }; /** * Abstraction of a sink for ranges. */ public static interface RangesOutput { /** * Output a rectangular range. * @param x x coordinate of the top-left corner of the range * @param y y coordinate of the top-left corner of the range * @param w width of the range * @param h height of the range * @return true if the output succeeded, false otherwise */ boolean outputRange(int x, int y, int w, int h); } /** * Outputs ranges of occupied pixels. * In a raster that has an alpha layer, a pixel is occupied if its alpha value is not null. * In a raster without alpha layer, a pixel is occupied if it is not completely black. * @param raster image to be segmented in non black or non-transparent ranges * @param out destination of the non null ranges * @return true if the output succeeded, false otherwise */ public static boolean outputOccupiedRanges(Raster raster, RangesOutput out) { Rectangle bounds = raster.getBounds(); SampleModel sampleModel = raster.getSampleModel(); boolean hasAlpha = sampleModel.getNumBands() == 4; // Try to use the underlying data array directly for a few common raster formats if (raster.getParent() == null && bounds.x == 0 && bounds.y == 0) { // No support for subraster (as obtained with Image.getSubimage(...)) DataBuffer data = raster.getDataBuffer(); if (data.getNumBanks() == 1) { // There is always a single bank for all BufferedImage types, except maybe TYPE_CUSTOM if (sampleModel instanceof MultiPixelPackedSampleModel) { MultiPixelPackedSampleModel packedSampleModel = (MultiPixelPackedSampleModel)sampleModel; if (packedSampleModel.getPixelBitStride() == 1) { // TYPE_BYTE_BINARY return outputOccupiedRangesOfBinaryPixels(((DataBufferByte)data).getData(), bounds.width, bounds.height, out); } } else if (sampleModel instanceof SinglePixelPackedSampleModel) { if (sampleModel.getDataType() == DataBuffer.TYPE_INT) { // TYPE_INT_ARGB, TYPE_INT_ARGB_PRE, TYPE_INT_BGR or TYPE_INT_RGB return outputOccupiedRanges(((DataBufferInt)data).getData(), bounds.width, bounds.height, hasAlpha ? 0xff000000 : 0xffffff, out); } // TODO could easily handle cases of TYPE_USHORT_GRAY and TYPE_BYTE_GRAY. } } } // Fallback behaviour : copy pixels of raster int[] pixels = raster.getPixels(0, 0, bounds.width, bounds.height, (int[])null); return outputOccupiedRanges(pixels, bounds.width, bounds.height, hasAlpha ? 0xff000000 : 0xffffff, out); } /** * Output the non-null values of a binary image as ranges of contiguous values. * @param binaryBits byte-packed binary bits of an image * @param w width of the image (in pixels) * @param h height of the image * @param out * @return true if the output succeeded, false otherwise */ public static boolean outputOccupiedRangesOfBinaryPixels(byte[] binaryBits, int w, int h, RangesOutput out) { Set rects = new HashSet(); Set prevLine = Collections.emptySet(); int scanlineBytes = binaryBits.length / h; for (int row = 0; row < h; row++) { Set curLine = new TreeSet(COMPARATOR); int rowOffsetBytes = row * scanlineBytes; int startCol = -1; // Look at each batch of 8 columns in this row for (int byteCol = 0; byteCol < scanlineBytes; byteCol++) { int firstByteCol = byteCol << 3; byte byteColBits = binaryBits[rowOffsetBytes + byteCol]; if (byteColBits == 0) { // all 8 bits are zeroes if (startCol >= 0) { // end of current region curLine.add(new Rectangle(startCol, row, firstByteCol - startCol, 1)); startCol = -1; } } else if (byteColBits == 0xff) { // all 8 bits are ones if (startCol < 0) { // start of new region startCol = firstByteCol; } } else { // mixed case : some bits are ones, others are zeroes for (int subCol = 0; subCol < 8; subCol++) { int col = firstByteCol | subCol; if ((byteColBits & subColMasks[subCol]) != 0) { if (startCol < 0) { // start of new region startCol = col; } } else { if (startCol >= 0) { // end of current region curLine.add(new Rectangle(startCol, row, col - startCol, 1)); startCol = -1; } } } } } if (startCol >= 0) { // end of last region curLine.add(new Rectangle(startCol, row, w - startCol, 1)); } Set unmerged = mergeRects(prevLine, curLine); rects.addAll(unmerged); prevLine = curLine; } // Add anything left over rects.addAll(prevLine); for (Iterator i=rects.iterator();i.hasNext();) { Rectangle r = i.next(); if (!out.outputRange(r.x, r.y, r.width, r.height)) { return false; } } return true; } /** * Output the occupied values of an integer-pixels image as ranges of contiguous values. * A pixel is considered occupied if the bitwise AND of its integer value with the provided occupationMask is not null. * @param pixels integer values of the pixels of an image * @param w width of the image (in pixels) * @param h height of the image * @param occupationMask mask used to select which bits are used in a pixel to check its occupied status. 0xff000000 would only take the alpha layer into account, for instance. * @param out where to output all the contiguous ranges of non occupied pixels * @return true if the output succeeded, false otherwise */ public static boolean outputOccupiedRanges(int[] pixels, int w, int h, int occupationMask, RangesOutput out) { Set rects = new HashSet(); Set prevLine = Collections.emptySet(); for (int row = 0; row < h; row++) { Set curLine = new TreeSet(COMPARATOR); int idxOffset = row * w; int startCol = -1; for (int col = 0; col < w; col++) { if ((pixels[idxOffset + col] & occupationMask) != 0) { if (startCol < 0) { startCol = col; } } else { if (startCol >= 0) { // end of current region curLine.add(new Rectangle(startCol, row, col-startCol, 1)); startCol = -1; } } } if (startCol >= 0) { // end of last region of current row curLine.add(new Rectangle(startCol, row, w-startCol, 1)); } Set unmerged = mergeRects(prevLine, curLine); rects.addAll(unmerged); prevLine = curLine; } // Add anything left over rects.addAll(prevLine); for (Iterator i=rects.iterator();i.hasNext();) { Rectangle r = i.next(); if (!out.outputRange(r.x, r.y, r.width, r.height)) { return false; } } return true; } private static Set mergeRects(Set prev, Set current) { Set unmerged = new HashSet(prev); if (!prev.isEmpty() && !current.isEmpty()) { Rectangle[] pr = prev.toArray(new Rectangle[0]); Rectangle[] cr = current.toArray(new Rectangle[0]); int ipr = 0; int icr = 0; while (ipr < pr.length && icr < cr.length) { while (cr[icr].x < pr[ipr].x) { if (++icr == cr.length) { return unmerged; } } if (cr[icr].x == pr[ipr].x && cr[icr].width == pr[ipr].width) { unmerged.remove(pr[ipr]); cr[icr].y = pr[ipr].y; cr[icr].height = pr[ipr].height + 1; ++icr; } else { ++ipr; } } } return unmerged; } }