Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sun.jna.platform.RasterRangesUtils Maven / Gradle / Ivy
/* 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;
}
}