/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.image;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.tk.Toolkit;
import javafx.geometry.Rectangle2D;
import javafx.util.Callback;
import java.lang.ref.WeakReference;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
The PixelBuffer
class represents pixel data that is constructed from a java.nio.Buffer
supplied by the application. A WritableImage
can use this PixelBuffer
directly without copying the pixel data. This PixelBuffer
can be shared among multiple WritableImage
s. Pixel data should be stored either in an IntBuffer
using a PixelFormat
of type INT_ARGB_PRE
or in a ByteBuffer
using a PixelFormat
of type BYTE_BGRA_PRE
. When the Buffer
is updated using the PixelBuffer.updateBuffer
method, all WritableImage
s that were created using this PixelBuffer
are redrawn. Example code that shows how to create a PixelBuffer
:
// Creating a PixelBuffer using BYTE_BGRA_PRE pixel format.
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteBgraPreInstance();
PixelBuffer<ByteBuffer> pixelBuffer = new PixelBuffer<>(width, height, byteBuffer, pixelFormat);
Image img = new WritableImage(pixelBuffer);
// Creating a PixelBuffer using INT_ARGB_PRE pixel format.
IntBuffer intBuffer = IntBuffer.allocate(width * height);
PixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();
PixelBuffer<IntBuffer> pixelBuffer = new PixelBuffer<>(width, height, intBuffer, pixelFormat);
Image img = new WritableImage(pixelBuffer);
Type parameters: - <T> – the type of
Buffer
that stores the pixel data. Only ByteBuffer
and IntBuffer
are supported.
See Also: Since: 13
/**
* The {@code PixelBuffer} class represents pixel data that is constructed from
* a {@link Buffer java.nio.Buffer} supplied by the application.
* A {@link WritableImage} can use this {@code PixelBuffer} directly without copying the pixel data.
* This {@code PixelBuffer} can be shared among multiple {@code WritableImage}s.
* Pixel data should be stored either in an {@link IntBuffer} using a {@link PixelFormat} of type
* {@code INT_ARGB_PRE} or in a {@link ByteBuffer} using a {@link PixelFormat} of type {@code BYTE_BGRA_PRE}.
* When the {@code Buffer} is updated using the {@link #updateBuffer PixelBuffer.updateBuffer} method,
* all {@code WritableImage}s that were created using this {@code PixelBuffer} are redrawn.
* <p>
* Example code that shows how to create a {@code PixelBuffer}:
* <pre>{@code // Creating a PixelBuffer using BYTE_BGRA_PRE pixel format.
* ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
* PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteBgraPreInstance();
* PixelBuffer<ByteBuffer> pixelBuffer = new PixelBuffer<>(width, height, byteBuffer, pixelFormat);
* Image img = new WritableImage(pixelBuffer);
*
* // Creating a PixelBuffer using INT_ARGB_PRE pixel format.
* IntBuffer intBuffer = IntBuffer.allocate(width * height);
* PixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();
* PixelBuffer<IntBuffer> pixelBuffer = new PixelBuffer<>(width, height, intBuffer, pixelFormat);
* Image img = new WritableImage(pixelBuffer);}</pre>
*
* @param <T> the type of {@code Buffer} that stores the pixel data.
* Only {@code ByteBuffer} and {@code IntBuffer} are supported.
* @see WritableImage#WritableImage(PixelBuffer)
* @since 13
*/
public class PixelBuffer<T extends Buffer> {
private final T buffer;
private final int width;
private final int height;
private final PixelFormat<T> pixelFormat;
private final List<WeakReference<WritableImage>> imageRefs;
Constructs a PixelBuffer
using the specified Buffer
and PixelFormat
. The type of the specified PixelFormat
must be either PixelFormat.Type.INT_ARGB_PRE
or PixelFormat.Type.BYTE_BGRA_PRE
. The constructor does not allocate memory to store the pixel data. The application must provide a buffer with sufficient memory for the combination of dimensions (width, height)
and the type of PixelFormat
. The PixelFormat.Type.INT_ARGB_PRE
requires an IntBuffer
with minimum capacity of width * height
, and the PixelFormat.Type.BYTE_BGRA_PRE
requires a ByteBuffer
with minimum capacity of width * height * 4
.
Params: - width – width in pixels of this
PixelBuffer
- height – height in pixels of this
PixelBuffer
- buffer – the buffer that stores the pixel data
- pixelFormat – the format of pixels in the
buffer
Throws: - IllegalArgumentException – if either
width
or height
is negative or zero, or if the type of pixelFormat
is unsupported, or if buffer
does not have sufficient memory, or if the type of buffer
and pixelFormat
do not match - NullPointerException – if
buffer
or pixelFormat
is null
/**
* Constructs a {@code PixelBuffer} using the specified {@code Buffer} and {@code PixelFormat}.
* The type of the specified {@code PixelFormat} must be either {@code PixelFormat.Type.INT_ARGB_PRE}
* or {@code PixelFormat.Type.BYTE_BGRA_PRE}.
* <p>The constructor does not allocate memory to store the pixel data. The application must provide
* a buffer with sufficient memory for the combination of dimensions {@code (width, height)} and the type
* of {@code PixelFormat}. The {@code PixelFormat.Type.INT_ARGB_PRE} requires an {@code IntBuffer} with
* minimum capacity of {@code width * height}, and the {@code PixelFormat.Type.BYTE_BGRA_PRE} requires
* a {@code ByteBuffer} with minimum capacity of {@code width * height * 4}.
*
* @param width width in pixels of this {@code PixelBuffer}
* @param height height in pixels of this {@code PixelBuffer}
* @param buffer the buffer that stores the pixel data
* @param pixelFormat the format of pixels in the {@code buffer}
* @throws IllegalArgumentException if either {@code width} or {@code height}
* is negative or zero, or if the type of {@code pixelFormat}
* is unsupported, or if {@code buffer} does
* not have sufficient memory, or if the type of {@code buffer}
* and {@code pixelFormat} do not match
* @throws NullPointerException if {@code buffer} or {@code pixelFormat} is {@code null}
*/
public PixelBuffer(int width, int height, T buffer, PixelFormat<T> pixelFormat) {
Objects.requireNonNull(buffer, "buffer must not be null.");
Objects.requireNonNull(pixelFormat, "pixelFormat must not be null.");
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("PixelBuffer dimensions must be positive (w,h > 0)");
}
switch (pixelFormat.getType()) {
case BYTE_BGRA_PRE:
if (buffer.capacity() / width / 4 < height) {
throw new IllegalArgumentException("Insufficient memory allocated for ByteBuffer.");
}
if (!(buffer instanceof ByteBuffer)) {
throw new IllegalArgumentException("PixelFormat<ByteBuffer> requires a ByteBuffer.");
}
break;
case INT_ARGB_PRE:
if (buffer.capacity() / width < height) {
throw new IllegalArgumentException("Insufficient memory allocated for IntBuffer.");
}
if (!(buffer instanceof IntBuffer)) {
throw new IllegalArgumentException("PixelFormat<IntBuffer> requires an IntBuffer.");
}
break;
default:
throw new IllegalArgumentException("Unsupported PixelFormat: " + pixelFormat.getType());
}
this.buffer = buffer;
this.width = width;
this.height = height;
this.pixelFormat = pixelFormat;
this.imageRefs = new LinkedList<>();
}
Returns the buffer
of this PixelBuffer
. Returns: the buffer
of this PixelBuffer
/**
* Returns the {@code buffer} of this {@code PixelBuffer}.
*
* @return the {@code buffer} of this {@code PixelBuffer}
*/
public T getBuffer() {
return buffer;
}
Returns the width
of this PixelBuffer
. Returns: the width
of this PixelBuffer
/**
* Returns the {@code width} of this {@code PixelBuffer}.
*
* @return the {@code width} of this {@code PixelBuffer}
*/
public int getWidth() {
return width;
}
Returns the height
of this PixelBuffer
. Returns: the height
of this PixelBuffer
/**
* Returns the {@code height} of this {@code PixelBuffer}.
*
* @return the {@code height} of this {@code PixelBuffer}
*/
public int getHeight() {
return height;
}
Returns the PixelFormat
of this PixelBuffer
. Returns: the PixelFormat
of this PixelBuffer
/**
* Returns the {@code PixelFormat} of this {@code PixelBuffer}.
*
* @return the {@code PixelFormat} of this {@code PixelBuffer}
*/
public PixelFormat<T> getPixelFormat() {
return pixelFormat;
}
Invokes the specified Callback
method and updates the dirty region of all WritableImage
s that were created using this PixelBuffer
. The Callback
method is expected to update the buffer and return a Rectangle2D
that encloses the dirty region, or return null
to indicate that the entire buffer is dirty. This method must be called on the JavaFX Application Thread.
Example code that shows how to use this method:
Callback<PixelBuffer<ByteBuffer>, Rectangle2D> callback = pixelBuffer -> {
ByteBuffer buffer = pixelBuffer.getBuffer();
// Update the buffer.
return new Rectangle2D(x, y, dirtyWidth, dirtyHeight);
};
pixelBuffer.updateBuffer(callback);
Params: - callback – the
Callback
method that updates the buffer
Throws: - IllegalStateException – if this method is called on a thread
other than the JavaFX Application Thread.
- NullPointerException – if
callback
is null
/**
* Invokes the specified {@code Callback} method and updates the dirty region of
* all {@code WritableImage}s that were created using this {@code PixelBuffer}.
* The {@code Callback} method is expected to update the buffer and
* return a {@code Rectangle2D} that encloses the dirty region, or
* return {@code null} to indicate that the entire buffer is dirty.
* <p>This method must be called on the JavaFX Application Thread.
* <p>Example code that shows how to use this method:
* <pre>{@code Callback<PixelBuffer<ByteBuffer>, Rectangle2D> callback = pixelBuffer -> {
* ByteBuffer buffer = pixelBuffer.getBuffer();
* // Update the buffer.
* return new Rectangle2D(x, y, dirtyWidth, dirtyHeight);
* };
* pixelBuffer.updateBuffer(callback);}</pre>
*
* @param callback the {@code Callback} method that updates the buffer
* @throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
* @throws NullPointerException if {@code callback} is {@code null}
**/
public void updateBuffer(Callback<PixelBuffer<T>, Rectangle2D> callback) {
Toolkit.getToolkit().checkFxUserThread();
Objects.requireNonNull(callback, "callback must not be null.");
Rectangle2D rect2D = callback.call(this);
if (rect2D != null) {
if (rect2D.getWidth() > 0 && rect2D.getHeight() > 0) {
int x1 = (int) Math.floor(rect2D.getMinX());
int y1 = (int) Math.floor(rect2D.getMinY());
int x2 = (int) Math.ceil(rect2D.getMaxX());
int y2 = (int) Math.ceil(rect2D.getMaxY());
bufferDirty(new Rectangle(x1, y1, x2 - x1, y2 - y1));
}
} else {
bufferDirty(null);
}
}
private void bufferDirty(Rectangle rect) {
Iterator<WeakReference<WritableImage>> iter = imageRefs.iterator();
while (iter.hasNext()) {
final WritableImage image = iter.next().get();
if (image != null) {
image.bufferDirty(rect);
} else {
iter.remove();
}
}
}
void addImage(WritableImage image) {
imageRefs.add(new WeakReference<>(image));
imageRefs.removeIf(imageRef -> (imageRef.get() == null));
}
}