632 lines
19 KiB
Java
632 lines
19 KiB
Java
package com.mojang.blaze3d.platform;
|
|
|
|
import com.mojang.jtracy.MemoryPool;
|
|
import com.mojang.jtracy.TracyClient;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.IntBuffer;
|
|
import java.nio.channels.WritableByteChannel;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.StandardOpenOption;
|
|
import java.util.EnumSet;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.function.IntUnaryOperator;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.client.gui.font.providers.FreeTypeUtil;
|
|
import net.minecraft.util.ARGB;
|
|
import net.minecraft.util.PngInfo;
|
|
import org.apache.commons.io.IOUtils;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.lwjgl.stb.STBIWriteCallback;
|
|
import org.lwjgl.stb.STBImage;
|
|
import org.lwjgl.stb.STBImageResize;
|
|
import org.lwjgl.stb.STBImageWrite;
|
|
import org.lwjgl.system.MemoryStack;
|
|
import org.lwjgl.system.MemoryUtil;
|
|
import org.lwjgl.util.freetype.FT_Bitmap;
|
|
import org.lwjgl.util.freetype.FT_Face;
|
|
import org.lwjgl.util.freetype.FT_GlyphSlot;
|
|
import org.lwjgl.util.freetype.FreeType;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public final class NativeImage implements AutoCloseable {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final MemoryPool MEMORY_POOL = TracyClient.createMemoryPool("NativeImage");
|
|
private static final Set<StandardOpenOption> OPEN_OPTIONS = EnumSet.of(
|
|
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
|
|
);
|
|
private final NativeImage.Format format;
|
|
private final int width;
|
|
private final int height;
|
|
private final boolean useStbFree;
|
|
private long pixels;
|
|
private final long size;
|
|
|
|
public NativeImage(int width, int height, boolean useCalloc) {
|
|
this(NativeImage.Format.RGBA, width, height, useCalloc);
|
|
}
|
|
|
|
public NativeImage(NativeImage.Format format, int width, int height, boolean useCalloc) {
|
|
if (width > 0 && height > 0) {
|
|
this.format = format;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.size = (long)width * height * format.components();
|
|
this.useStbFree = false;
|
|
if (useCalloc) {
|
|
this.pixels = MemoryUtil.nmemCalloc(1L, this.size);
|
|
} else {
|
|
this.pixels = MemoryUtil.nmemAlloc(this.size);
|
|
}
|
|
|
|
MEMORY_POOL.malloc(this.pixels, (int)this.size);
|
|
if (this.pixels == 0L) {
|
|
throw new IllegalStateException("Unable to allocate texture of size " + width + "x" + height + " (" + format.components() + " channels)");
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Invalid texture size: " + width + "x" + height);
|
|
}
|
|
}
|
|
|
|
public NativeImage(NativeImage.Format format, int width, int height, boolean useStbFree, long pixels) {
|
|
if (width > 0 && height > 0) {
|
|
this.format = format;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.useStbFree = useStbFree;
|
|
this.pixels = pixels;
|
|
this.size = (long)width * height * format.components();
|
|
} else {
|
|
throw new IllegalArgumentException("Invalid texture size: " + width + "x" + height);
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return "NativeImage[" + this.format + " " + this.width + "x" + this.height + "@" + this.pixels + (this.useStbFree ? "S" : "N") + "]";
|
|
}
|
|
|
|
private boolean isOutsideBounds(int x, int y) {
|
|
return x < 0 || x >= this.width || y < 0 || y >= this.height;
|
|
}
|
|
|
|
public static NativeImage read(InputStream textureStream) throws IOException {
|
|
return read(NativeImage.Format.RGBA, textureStream);
|
|
}
|
|
|
|
public static NativeImage read(@Nullable NativeImage.Format format, InputStream textureStream) throws IOException {
|
|
ByteBuffer byteBuffer = null;
|
|
|
|
NativeImage var3;
|
|
try {
|
|
byteBuffer = TextureUtil.readResource(textureStream);
|
|
byteBuffer.rewind();
|
|
var3 = read(format, byteBuffer);
|
|
} finally {
|
|
MemoryUtil.memFree(byteBuffer);
|
|
IOUtils.closeQuietly(textureStream);
|
|
}
|
|
|
|
return var3;
|
|
}
|
|
|
|
public static NativeImage read(ByteBuffer textureData) throws IOException {
|
|
return read(NativeImage.Format.RGBA, textureData);
|
|
}
|
|
|
|
public static NativeImage read(byte[] bytes) throws IOException {
|
|
MemoryStack memoryStack = MemoryStack.stackGet();
|
|
int i = memoryStack.getPointer();
|
|
if (i < bytes.length) {
|
|
ByteBuffer byteBuffer = MemoryUtil.memAlloc(bytes.length);
|
|
|
|
NativeImage var13;
|
|
try {
|
|
var13 = putAndRead(byteBuffer, bytes);
|
|
} finally {
|
|
MemoryUtil.memFree(byteBuffer);
|
|
}
|
|
|
|
return var13;
|
|
} else {
|
|
NativeImage var5;
|
|
try (MemoryStack memoryStack2 = MemoryStack.stackPush()) {
|
|
ByteBuffer byteBuffer2 = memoryStack2.malloc(bytes.length);
|
|
var5 = putAndRead(byteBuffer2, bytes);
|
|
}
|
|
|
|
return var5;
|
|
}
|
|
}
|
|
|
|
private static NativeImage putAndRead(ByteBuffer buffer, byte[] bytes) throws IOException {
|
|
buffer.put(bytes);
|
|
buffer.rewind();
|
|
return read(buffer);
|
|
}
|
|
|
|
public static NativeImage read(@Nullable NativeImage.Format format, ByteBuffer textureData) throws IOException {
|
|
if (format != null && !format.supportedByStb()) {
|
|
throw new UnsupportedOperationException("Don't know how to read format " + format);
|
|
} else if (MemoryUtil.memAddress(textureData) == 0L) {
|
|
throw new IllegalArgumentException("Invalid buffer");
|
|
} else {
|
|
PngInfo.validateHeader(textureData);
|
|
|
|
NativeImage var9;
|
|
try (MemoryStack memoryStack = MemoryStack.stackPush()) {
|
|
IntBuffer intBuffer = memoryStack.mallocInt(1);
|
|
IntBuffer intBuffer2 = memoryStack.mallocInt(1);
|
|
IntBuffer intBuffer3 = memoryStack.mallocInt(1);
|
|
ByteBuffer byteBuffer = STBImage.stbi_load_from_memory(textureData, intBuffer, intBuffer2, intBuffer3, format == null ? 0 : format.components);
|
|
if (byteBuffer == null) {
|
|
throw new IOException("Could not load image: " + STBImage.stbi_failure_reason());
|
|
}
|
|
|
|
long l = MemoryUtil.memAddress(byteBuffer);
|
|
MEMORY_POOL.malloc(l, byteBuffer.limit());
|
|
var9 = new NativeImage(format == null ? NativeImage.Format.getStbFormat(intBuffer3.get(0)) : format, intBuffer.get(0), intBuffer2.get(0), true, l);
|
|
}
|
|
|
|
return var9;
|
|
}
|
|
}
|
|
|
|
private void checkAllocated() {
|
|
if (this.pixels == 0L) {
|
|
throw new IllegalStateException("Image is not allocated.");
|
|
}
|
|
}
|
|
|
|
public void close() {
|
|
if (this.pixels != 0L) {
|
|
if (this.useStbFree) {
|
|
STBImage.nstbi_image_free(this.pixels);
|
|
} else {
|
|
MemoryUtil.nmemFree(this.pixels);
|
|
}
|
|
|
|
MEMORY_POOL.free(this.pixels);
|
|
}
|
|
|
|
this.pixels = 0L;
|
|
}
|
|
|
|
public int getWidth() {
|
|
return this.width;
|
|
}
|
|
|
|
public int getHeight() {
|
|
return this.height;
|
|
}
|
|
|
|
public NativeImage.Format format() {
|
|
return this.format;
|
|
}
|
|
|
|
private int getPixelABGR(int x, int y) {
|
|
if (this.format != NativeImage.Format.RGBA) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "getPixelRGBA only works on RGBA images; have %s", this.format));
|
|
} else if (this.isOutsideBounds(x, y)) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", x, y, this.width, this.height));
|
|
} else {
|
|
this.checkAllocated();
|
|
long l = (x + (long)y * this.width) * 4L;
|
|
return MemoryUtil.memGetInt(this.pixels + l);
|
|
}
|
|
}
|
|
|
|
public int getPixel(int x, int y) {
|
|
return ARGB.fromABGR(this.getPixelABGR(x, y));
|
|
}
|
|
|
|
public void setPixelABGR(int x, int y, int color) {
|
|
if (this.format != NativeImage.Format.RGBA) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "setPixelRGBA only works on RGBA images; have %s", this.format));
|
|
} else if (this.isOutsideBounds(x, y)) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", x, y, this.width, this.height));
|
|
} else {
|
|
this.checkAllocated();
|
|
long l = (x + (long)y * this.width) * 4L;
|
|
MemoryUtil.memPutInt(this.pixels + l, color);
|
|
}
|
|
}
|
|
|
|
public void setPixel(int x, int y, int color) {
|
|
this.setPixelABGR(x, y, ARGB.toABGR(color));
|
|
}
|
|
|
|
public NativeImage mappedCopy(IntUnaryOperator function) {
|
|
if (this.format != NativeImage.Format.RGBA) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "function application only works on RGBA images; have %s", this.format));
|
|
} else {
|
|
this.checkAllocated();
|
|
NativeImage nativeImage = new NativeImage(this.width, this.height, false);
|
|
int i = this.width * this.height;
|
|
IntBuffer intBuffer = MemoryUtil.memIntBuffer(this.pixels, i);
|
|
IntBuffer intBuffer2 = MemoryUtil.memIntBuffer(nativeImage.pixels, i);
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
int k = ARGB.fromABGR(intBuffer.get(j));
|
|
int l = function.applyAsInt(k);
|
|
intBuffer2.put(j, ARGB.toABGR(l));
|
|
}
|
|
|
|
return nativeImage;
|
|
}
|
|
}
|
|
|
|
public int[] getPixelsABGR() {
|
|
if (this.format != NativeImage.Format.RGBA) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "getPixels only works on RGBA images; have %s", this.format));
|
|
} else {
|
|
this.checkAllocated();
|
|
int[] is = new int[this.width * this.height];
|
|
MemoryUtil.memIntBuffer(this.pixels, this.width * this.height).get(is);
|
|
return is;
|
|
}
|
|
}
|
|
|
|
public int[] getPixels() {
|
|
int[] is = this.getPixelsABGR();
|
|
|
|
for (int i = 0; i < is.length; i++) {
|
|
is[i] = ARGB.fromABGR(is[i]);
|
|
}
|
|
|
|
return is;
|
|
}
|
|
|
|
public byte getLuminanceOrAlpha(int x, int y) {
|
|
if (!this.format.hasLuminanceOrAlpha()) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "no luminance or alpha in %s", this.format));
|
|
} else if (this.isOutsideBounds(x, y)) {
|
|
throw new IllegalArgumentException(String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", x, y, this.width, this.height));
|
|
} else {
|
|
int i = (x + y * this.width) * this.format.components() + this.format.luminanceOrAlphaOffset() / 8;
|
|
return MemoryUtil.memGetByte(this.pixels + i);
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public int[] makePixelArray() {
|
|
if (this.format != NativeImage.Format.RGBA) {
|
|
throw new UnsupportedOperationException("can only call makePixelArray for RGBA images.");
|
|
} else {
|
|
this.checkAllocated();
|
|
int[] is = new int[this.getWidth() * this.getHeight()];
|
|
|
|
for (int i = 0; i < this.getHeight(); i++) {
|
|
for (int j = 0; j < this.getWidth(); j++) {
|
|
is[j + i * this.getWidth()] = this.getPixel(j, i);
|
|
}
|
|
}
|
|
|
|
return is;
|
|
}
|
|
}
|
|
|
|
public void writeToFile(File file) throws IOException {
|
|
this.writeToFile(file.toPath());
|
|
}
|
|
|
|
public boolean copyFromFont(FT_Face face, int index) {
|
|
if (this.format.components() != 1) {
|
|
throw new IllegalArgumentException("Can only write fonts into 1-component images.");
|
|
} else if (FreeTypeUtil.checkError(FreeType.FT_Load_Glyph(face, index, 4), "Loading glyph")) {
|
|
return false;
|
|
} else {
|
|
FT_GlyphSlot fT_GlyphSlot = (FT_GlyphSlot)Objects.requireNonNull(face.glyph(), "Glyph not initialized");
|
|
FT_Bitmap fT_Bitmap = fT_GlyphSlot.bitmap();
|
|
if (fT_Bitmap.pixel_mode() != 2) {
|
|
throw new IllegalStateException("Rendered glyph was not 8-bit grayscale");
|
|
} else if (fT_Bitmap.width() == this.getWidth() && fT_Bitmap.rows() == this.getHeight()) {
|
|
int i = fT_Bitmap.width() * fT_Bitmap.rows();
|
|
ByteBuffer byteBuffer = (ByteBuffer)Objects.requireNonNull(fT_Bitmap.buffer(i), "Glyph has no bitmap");
|
|
MemoryUtil.memCopy(MemoryUtil.memAddress(byteBuffer), this.pixels, i);
|
|
return true;
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
String.format(
|
|
Locale.ROOT, "Glyph bitmap of size %sx%s does not match image of size: %sx%s", fT_Bitmap.width(), fT_Bitmap.rows(), this.getWidth(), this.getHeight()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void writeToFile(Path path) throws IOException {
|
|
if (!this.format.supportedByStb()) {
|
|
throw new UnsupportedOperationException("Don't know how to write format " + this.format);
|
|
} else {
|
|
this.checkAllocated();
|
|
WritableByteChannel writableByteChannel = Files.newByteChannel(path, OPEN_OPTIONS);
|
|
|
|
try {
|
|
if (!this.writeToChannel(writableByteChannel)) {
|
|
throw new IOException("Could not write image to the PNG file \"" + path.toAbsolutePath() + "\": " + STBImage.stbi_failure_reason());
|
|
}
|
|
} catch (Throwable var6) {
|
|
if (writableByteChannel != null) {
|
|
try {
|
|
writableByteChannel.close();
|
|
} catch (Throwable var5) {
|
|
var6.addSuppressed(var5);
|
|
}
|
|
}
|
|
|
|
throw var6;
|
|
}
|
|
|
|
if (writableByteChannel != null) {
|
|
writableByteChannel.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean writeToChannel(WritableByteChannel channel) throws IOException {
|
|
NativeImage.WriteCallback writeCallback = new NativeImage.WriteCallback(channel);
|
|
|
|
boolean var4;
|
|
try {
|
|
int i = Math.min(this.getHeight(), Integer.MAX_VALUE / this.getWidth() / this.format.components());
|
|
if (i < this.getHeight()) {
|
|
LOGGER.warn("Dropping image height from {} to {} to fit the size into 32-bit signed int", this.getHeight(), i);
|
|
}
|
|
|
|
if (STBImageWrite.nstbi_write_png_to_func(writeCallback.address(), 0L, this.getWidth(), i, this.format.components(), this.pixels, 0) != 0) {
|
|
writeCallback.throwIfException();
|
|
return true;
|
|
}
|
|
|
|
var4 = false;
|
|
} finally {
|
|
writeCallback.free();
|
|
}
|
|
|
|
return var4;
|
|
}
|
|
|
|
public void copyFrom(NativeImage other) {
|
|
if (other.format() != this.format) {
|
|
throw new UnsupportedOperationException("Image formats don't match.");
|
|
} else {
|
|
int i = this.format.components();
|
|
this.checkAllocated();
|
|
other.checkAllocated();
|
|
if (this.width == other.width) {
|
|
MemoryUtil.memCopy(other.pixels, this.pixels, Math.min(this.size, other.size));
|
|
} else {
|
|
int j = Math.min(this.getWidth(), other.getWidth());
|
|
int k = Math.min(this.getHeight(), other.getHeight());
|
|
|
|
for (int l = 0; l < k; l++) {
|
|
int m = l * other.getWidth() * i;
|
|
int n = l * this.getWidth() * i;
|
|
MemoryUtil.memCopy(other.pixels + m, this.pixels + n, j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void fillRect(int x, int y, int width, int height, int value) {
|
|
for (int i = y; i < y + height; i++) {
|
|
for (int j = x; j < x + width; j++) {
|
|
this.setPixel(j, i, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void copyRect(int xFrom, int yFrom, int xToDelta, int yToDelta, int width, int height, boolean mirrorX, boolean mirrorY) {
|
|
this.copyRect(this, xFrom, yFrom, xFrom + xToDelta, yFrom + yToDelta, width, height, mirrorX, mirrorY);
|
|
}
|
|
|
|
public void copyRect(NativeImage source, int xFrom, int yFrom, int xTo, int yTo, int width, int height, boolean mirrorX, boolean mirrorY) {
|
|
for (int i = 0; i < height; i++) {
|
|
for (int j = 0; j < width; j++) {
|
|
int k = mirrorX ? width - 1 - j : j;
|
|
int l = mirrorY ? height - 1 - i : i;
|
|
int m = this.getPixelABGR(xFrom + j, yFrom + i);
|
|
source.setPixelABGR(xTo + k, yTo + l, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void resizeSubRectTo(int x, int y, int width, int height, NativeImage image) {
|
|
this.checkAllocated();
|
|
if (image.format() != this.format) {
|
|
throw new UnsupportedOperationException("resizeSubRectTo only works for images of the same format.");
|
|
} else {
|
|
int i = this.format.components();
|
|
STBImageResize.nstbir_resize_uint8(
|
|
this.pixels + (x + y * this.getWidth()) * i, width, height, this.getWidth() * i, image.pixels, image.getWidth(), image.getHeight(), 0, i
|
|
);
|
|
}
|
|
}
|
|
|
|
public void untrack() {
|
|
DebugMemoryUntracker.untrack(this.pixels);
|
|
}
|
|
|
|
public long getPointer() {
|
|
return this.pixels;
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static enum Format {
|
|
RGBA(4, true, true, true, false, true, 0, 8, 16, 255, 24, true),
|
|
RGB(3, true, true, true, false, false, 0, 8, 16, 255, 255, true),
|
|
LUMINANCE_ALPHA(2, false, false, false, true, true, 255, 255, 255, 0, 8, true),
|
|
LUMINANCE(1, false, false, false, true, false, 0, 0, 0, 0, 255, true);
|
|
|
|
final int components;
|
|
private final boolean hasRed;
|
|
private final boolean hasGreen;
|
|
private final boolean hasBlue;
|
|
private final boolean hasLuminance;
|
|
private final boolean hasAlpha;
|
|
private final int redOffset;
|
|
private final int greenOffset;
|
|
private final int blueOffset;
|
|
private final int luminanceOffset;
|
|
private final int alphaOffset;
|
|
private final boolean supportedByStb;
|
|
|
|
private Format(
|
|
final int components,
|
|
final boolean hasRed,
|
|
final boolean hasGreen,
|
|
final boolean hasBlue,
|
|
final boolean hasLuminance,
|
|
final boolean hasAlpha,
|
|
final int redOffset,
|
|
final int greenOffset,
|
|
final int blueOffset,
|
|
final int luminanceOffset,
|
|
final int alphaOffset,
|
|
final boolean supportedByStb
|
|
) {
|
|
this.components = components;
|
|
this.hasRed = hasRed;
|
|
this.hasGreen = hasGreen;
|
|
this.hasBlue = hasBlue;
|
|
this.hasLuminance = hasLuminance;
|
|
this.hasAlpha = hasAlpha;
|
|
this.redOffset = redOffset;
|
|
this.greenOffset = greenOffset;
|
|
this.blueOffset = blueOffset;
|
|
this.luminanceOffset = luminanceOffset;
|
|
this.alphaOffset = alphaOffset;
|
|
this.supportedByStb = supportedByStb;
|
|
}
|
|
|
|
public int components() {
|
|
return this.components;
|
|
}
|
|
|
|
public boolean hasRed() {
|
|
return this.hasRed;
|
|
}
|
|
|
|
public boolean hasGreen() {
|
|
return this.hasGreen;
|
|
}
|
|
|
|
public boolean hasBlue() {
|
|
return this.hasBlue;
|
|
}
|
|
|
|
public boolean hasLuminance() {
|
|
return this.hasLuminance;
|
|
}
|
|
|
|
public boolean hasAlpha() {
|
|
return this.hasAlpha;
|
|
}
|
|
|
|
public int redOffset() {
|
|
return this.redOffset;
|
|
}
|
|
|
|
public int greenOffset() {
|
|
return this.greenOffset;
|
|
}
|
|
|
|
public int blueOffset() {
|
|
return this.blueOffset;
|
|
}
|
|
|
|
public int luminanceOffset() {
|
|
return this.luminanceOffset;
|
|
}
|
|
|
|
public int alphaOffset() {
|
|
return this.alphaOffset;
|
|
}
|
|
|
|
public boolean hasLuminanceOrRed() {
|
|
return this.hasLuminance || this.hasRed;
|
|
}
|
|
|
|
public boolean hasLuminanceOrGreen() {
|
|
return this.hasLuminance || this.hasGreen;
|
|
}
|
|
|
|
public boolean hasLuminanceOrBlue() {
|
|
return this.hasLuminance || this.hasBlue;
|
|
}
|
|
|
|
public boolean hasLuminanceOrAlpha() {
|
|
return this.hasLuminance || this.hasAlpha;
|
|
}
|
|
|
|
public int luminanceOrRedOffset() {
|
|
return this.hasLuminance ? this.luminanceOffset : this.redOffset;
|
|
}
|
|
|
|
public int luminanceOrGreenOffset() {
|
|
return this.hasLuminance ? this.luminanceOffset : this.greenOffset;
|
|
}
|
|
|
|
public int luminanceOrBlueOffset() {
|
|
return this.hasLuminance ? this.luminanceOffset : this.blueOffset;
|
|
}
|
|
|
|
public int luminanceOrAlphaOffset() {
|
|
return this.hasLuminance ? this.luminanceOffset : this.alphaOffset;
|
|
}
|
|
|
|
public boolean supportedByStb() {
|
|
return this.supportedByStb;
|
|
}
|
|
|
|
static NativeImage.Format getStbFormat(int channels) {
|
|
switch (channels) {
|
|
case 1:
|
|
return LUMINANCE;
|
|
case 2:
|
|
return LUMINANCE_ALPHA;
|
|
case 3:
|
|
return RGB;
|
|
case 4:
|
|
default:
|
|
return RGBA;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
static class WriteCallback extends STBIWriteCallback {
|
|
private final WritableByteChannel output;
|
|
@Nullable
|
|
private IOException exception;
|
|
|
|
WriteCallback(WritableByteChannel output) {
|
|
this.output = output;
|
|
}
|
|
|
|
@Override
|
|
public void invoke(long l, long m, int i) {
|
|
ByteBuffer byteBuffer = getData(m, i);
|
|
|
|
try {
|
|
this.output.write(byteBuffer);
|
|
} catch (IOException var8) {
|
|
this.exception = var8;
|
|
}
|
|
}
|
|
|
|
public void throwIfException() throws IOException {
|
|
if (this.exception != null) {
|
|
throw this.exception;
|
|
}
|
|
}
|
|
}
|
|
}
|