www in docker support

This commit is contained in:
MaddoScientisto 2026-04-22 18:41:37 +02:00
commit c227fce036
2145 changed files with 399596 additions and 58 deletions

View file

@ -0,0 +1,68 @@
package org.jcodec.codecs.png;
import java.nio.ByteBuffer;
import org.jcodec.common.model.ColorSpace;
class IHDR {
static final int PNG_COLOR_MASK_ALPHA = 4;
static final int PNG_COLOR_MASK_COLOR = 2;
static final int PNG_COLOR_MASK_PALETTE = 1;
int width;
int height;
byte bitDepth;
byte colorType;
private byte compressionType;
private byte filterType;
byte interlaceType;
void write(ByteBuffer data) {
data.putInt(this.width);
data.putInt(this.height);
data.put(this.bitDepth);
data.put(this.colorType);
data.put(this.compressionType);
data.put(this.filterType);
data.put(this.interlaceType);
}
void parse(ByteBuffer data) {
this.width = data.getInt();
this.height = data.getInt();
this.bitDepth = data.get();
this.colorType = data.get();
this.compressionType = data.get();
this.filterType = data.get();
this.interlaceType = data.get();
data.getInt();
}
int rowSize() {
return this.width * getBitsPerPixel() + 7 >> 3;
}
private int getNBChannels() {
int channels = 1;
if ((this.colorType & 0x3) == 2)
channels = 3;
if ((this.colorType & 0x4) != 0)
channels++;
return channels;
}
int getBitsPerPixel() {
return this.bitDepth * getNBChannels();
}
ColorSpace colorSpace() {
return ColorSpace.RGB;
}
}

View file

@ -0,0 +1,23 @@
package org.jcodec.codecs.png;
public class PNGConsts {
static final long PNGSIG = -8552249625308161526L;
static final int PNGSIGhi = -1991225785;
static final int MNGSIGhi = -1974645177;
static final int PNGSIGlo = 218765834;
static final int MNGSIGlo = 218765834;
static final int TAG_IHDR = 1229472850;
static final int TAG_IDAT = 1229209940;
static final int TAG_PLTE = 1347179589;
static final int TAG_tRNS = 1951551059;
static final int TAG_IEND = 1229278788;
}

View file

@ -0,0 +1,461 @@
package org.jcodec.codecs.png;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.common.tools.MathUtil;
public class PNGDecoder extends VideoDecoder {
private static final int FILTER_TYPE_LOCO = 64;
private static final int FILTER_VALUE_NONE = 0;
private static final int FILTER_VALUE_SUB = 1;
private static final int FILTER_VALUE_UP = 2;
private static final int FILTER_VALUE_AVG = 3;
private static final int FILTER_VALUE_PAETH = 4;
private static final int PNG_COLOR_TYPE_GRAY = 0;
private static final int PNG_COLOR_TYPE_PALETTE = 3;
private static final int PNG_COLOR_TYPE_RGB = 2;
private static final int alphaR = 127;
private static final int alphaG = 127;
private static final int alphaB = 127;
private static final int[] logPassStep = new int[] { 3, 3, 2, 2, 1, 1, 0 };
private static final int[] logPassRowStep = new int[] { 3, 3, 3, 2, 2, 1, 1 };
private static final int[] passOff = new int[] { 0, 4, 0, 2, 0, 1, 0 };
private static final int[] passRowOff = new int[] { 0, 0, 4, 0, 2, 0, 1 };
private byte[] ca = new byte[4];
public Picture decodeFrame(ByteBuffer data, byte[][] buffer) {
if (!ispng(data))
throw new RuntimeException("Not a PNG file.");
IHDR ihdr = null;
PLTE plte = null;
TRNS trns = null;
List<ByteBuffer> list = new ArrayList<>();
while (data.remaining() >= 8) {
int length = data.getInt();
int tag = data.getInt();
if (data.remaining() < length)
break;
switch (tag) {
case 1229472850:
ihdr = new IHDR();
ihdr.parse(data);
continue;
case 1347179589:
plte = new PLTE();
plte.parse(data, length);
continue;
case 1951551059:
if (ihdr == null)
throw new IllegalStateException("tRNS tag before IHDR");
trns = new TRNS(ihdr.colorType);
trns.parse(data, length);
continue;
case 1229209940:
list.add(NIOUtils.read(data, length));
NIOUtils.skip(data, 4);
continue;
case 1229278788:
NIOUtils.skip(data, 4);
continue;
}
data.position(data.position() + length + 4);
}
if (ihdr != null) {
try {
decodeData(ihdr, plte, trns, list, buffer);
} catch (DataFormatException e) {
return null;
}
return Picture.createPicture(ihdr.width, ihdr.height, buffer, ihdr.colorSpace());
}
throw new IllegalStateException("no IHDR tag");
}
private void decodeData(IHDR ihdr, PLTE plte, TRNS trns, List<ByteBuffer> list, byte[][] buffer) throws DataFormatException {
int bpp = ihdr.getBitsPerPixel() + 7 >> 3;
int passes = (ihdr.interlaceType == 0) ? 1 : 7;
Inflater inflater = new Inflater();
Iterator<ByteBuffer> it = list.iterator();
for (int pass = 0; pass < passes; pass++) {
int rowSize, rowStart, rowStep, colStart, colStep;
if (ihdr.interlaceType == 0) {
rowSize = ihdr.rowSize() + 1;
colStart = rowStart = 0;
colStep = rowStep = 1;
} else {
int round = (1 << logPassStep[pass]) - 1;
rowSize = (ihdr.width + round >> logPassStep[pass]) + 1;
rowStart = passRowOff[pass];
rowStep = 1 << logPassRowStep[pass];
colStart = passOff[pass];
colStep = 1 << logPassStep[pass];
}
byte[] lastRow = new byte[rowSize - 1];
byte[] uncompressed = new byte[rowSize];
int bptr = 3 * (ihdr.width * rowStart + colStart);
for (int row = rowStart; row < ihdr.height; row += rowStep) {
int count = inflater.inflate(uncompressed);
if (count < uncompressed.length && inflater.needsInput()) {
if (!it.hasNext()) {
Logger.warn(String.format("Data truncation at row %d", row));
break;
}
ByteBuffer next = it.next();
inflater.setInput(NIOUtils.toArray(next));
int toRead = uncompressed.length - count;
count = inflater.inflate(uncompressed, count, toRead);
if (count != toRead) {
Logger.warn(String.format("Data truncation at row %d", row));
break;
}
}
int filter = uncompressed[0];
switch (filter) {
case 0:
System.arraycopy(uncompressed, 1, lastRow, 0, rowSize - 1);
break;
case 1:
filterSub(uncompressed, rowSize - 1, lastRow, bpp);
break;
case 2:
filterUp(uncompressed, rowSize - 1, lastRow);
break;
case 3:
filterAvg(uncompressed, rowSize - 1, lastRow, bpp);
break;
case 4:
filterPaeth(uncompressed, rowSize - 1, lastRow, bpp);
break;
}
int bptrWas = bptr;
if ((ihdr.colorType & 0x1) != 0) {
for (int i = 0; i < rowSize - 1; i += bpp, bptr += 3 * colStep) {
int plt = plte.palette[lastRow[i] & 0xFF];
buffer[0][bptr] = (byte)((plt >> 16 & 0xFF) - 128);
buffer[0][bptr + 1] = (byte)((plt >> 8 & 0xFF) - 128);
buffer[0][bptr + 2] = (byte)((plt & 0xFF) - 128);
}
} else if ((ihdr.colorType & 0x2) != 0) {
for (int i = 0; i < rowSize - 1; i += bpp, bptr += 3 * colStep) {
buffer[0][bptr] = (byte)((lastRow[i] & 0xFF) - 128);
buffer[0][bptr + 1] = (byte)((lastRow[i + 1] & 0xFF) - 128);
buffer[0][bptr + 2] = (byte)((lastRow[i + 2] & 0xFF) - 128);
}
} else {
for (int i = 0; i < rowSize - 1; i += bpp, bptr += 3 * colStep) {
buffer[0][bptr + 2] = (byte)((lastRow[i] & 0xFF) - 128);
buffer[0][bptr + 1] = (byte)((lastRow[i] & 0xFF) - 128);
buffer[0][bptr] = (byte)((lastRow[i] & 0xFF) - 128);
}
}
if ((ihdr.colorType & 0x4) != 0) {
for (int i = bpp - 1, j = bptrWas; i < rowSize - 1; i += bpp, j += 3 * colStep) {
int alpha = lastRow[i] & 0xFF, nalpha = 256 - alpha;
buffer[0][j] = (byte)(127 * nalpha + buffer[0][j] * alpha >> 8);
buffer[0][j + 1] = (byte)(127 * nalpha + buffer[0][j + 1] * alpha >> 8);
buffer[0][j + 2] = (byte)(127 * nalpha + buffer[0][j + 2] * alpha >> 8);
}
} else if (trns != null) {
if (ihdr.colorType == 3) {
for (int i = 0, j = bptrWas; i < rowSize - 1; i++, j += 3 * colStep) {
int alpha = trns.alphaPal[lastRow[i] & 0xFF] & 0xFF, nalpha = 256 - alpha;
buffer[0][j] = (byte)(127 * nalpha + buffer[0][j] * alpha >> 8);
buffer[0][j + 1] = (byte)(127 * nalpha + buffer[0][j + 1] * alpha >> 8);
buffer[0][j + 2] = (byte)(127 * nalpha + buffer[0][j + 2] * alpha >> 8);
}
} else if (ihdr.colorType == 2) {
int ar = (trns.alphaR & 0xFF) - 128;
int ag = (trns.alphaG & 0xFF) - 128;
int ab = (trns.alphaB & 0xFF) - 128;
if (ab != 127 || ag != 127 || ar != 127)
for (int i = 0, j = bptrWas; i < rowSize - 1; i += bpp, j += 3 * colStep) {
if (buffer[0][j] == ar && buffer[0][j + 1] == ag && buffer[0][j + 2] == ab) {
buffer[0][j] = Byte.MAX_VALUE;
buffer[0][j + 1] = Byte.MAX_VALUE;
buffer[0][j + 2] = Byte.MAX_VALUE;
}
}
} else if (ihdr.colorType == 0) {
for (int i = 0, j = bptrWas; i < rowSize - 1; i++, j += 3 * colStep) {
if (lastRow[i] == trns.alphaGrey) {
buffer[0][j] = Byte.MAX_VALUE;
buffer[0][j + 1] = Byte.MAX_VALUE;
buffer[0][j + 2] = Byte.MAX_VALUE;
}
}
}
}
bptr = bptrWas + 3 * ihdr.width * rowStep;
}
}
}
private void filterPaeth(byte[] uncompressed, int rowSize, byte[] lastRow, int bpp) {
for (int j = 0; j < bpp; j++) {
this.ca[j] = lastRow[j];
lastRow[j] = (byte)((uncompressed[j + 1] & 0xFF) + (lastRow[j] & 0xFF));
}
for (int i = bpp; i < rowSize; i++) {
int a = lastRow[i - bpp] & 0xFF;
int b = lastRow[i] & 0xFF;
int c = this.ca[i % bpp] & 0xFF;
int p = b - c;
int pc = a - c;
int pa = MathUtil.abs(p);
int pb = MathUtil.abs(pc);
pc = MathUtil.abs(p + pc);
if (pa <= pb && pa <= pc) {
p = a;
} else if (pb <= pc) {
p = b;
} else {
p = c;
}
this.ca[i % bpp] = lastRow[i];
lastRow[i] = (byte)(p + (uncompressed[i + 1] & 0xFF));
}
}
private static void filterSub(byte[] uncompressed, int rowSize, byte[] lastRow, int bpp) {
switch (bpp) {
case 1:
filterSub1(uncompressed, lastRow, rowSize);
break;
case 2:
filterSub2(uncompressed, lastRow, rowSize);
break;
case 3:
filterSub3(uncompressed, lastRow, rowSize);
break;
default:
filterSub4(uncompressed, lastRow, rowSize);
break;
}
}
private static void filterAvg(byte[] uncompressed, int rowSize, byte[] lastRow, int bpp) {
switch (bpp) {
case 1:
filterAvg1(uncompressed, lastRow, rowSize);
break;
case 2:
filterAvg2(uncompressed, lastRow, rowSize);
break;
case 3:
filterAvg3(uncompressed, lastRow, rowSize);
break;
default:
filterAvg4(uncompressed, lastRow, rowSize);
break;
}
}
private static void filterSub1(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p = lastRow[0] = uncompressed[1];
for (int i = 1; i < rowSize; i++)
p = lastRow[i] = (byte)((p & 0xFF) + (uncompressed[i + 1] & 0xFF));
}
private static void filterUp(byte[] uncompressed, int rowSize, byte[] lastRow) {
for (int i = 0; i < rowSize; i++)
lastRow[i] = (byte)((lastRow[i] & 0xFF) + (uncompressed[i + 1] & 0xFF));
}
private static void filterAvg1(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p = lastRow[0] = (byte)((uncompressed[1] & 0xFF) + ((lastRow[0] & 0xFF) >> 1));
for (int i = 1; i < rowSize; i++)
p = lastRow[i] = (byte)(((lastRow[i] & 0xFF) + (p & 0xFF) >> 1) + (uncompressed[i + 1] & 0xFF));
}
private static void filterSub2(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = uncompressed[1];
byte p1 = lastRow[1] = uncompressed[2];
for (int i = 2; i < rowSize; i += 2) {
p0 = lastRow[i] = (byte)((p0 & 0xFF) + (uncompressed[1 + i] & 0xFF));
p1 = lastRow[i + 1] = (byte)((p1 & 0xFF) + (uncompressed[2 + i] & 0xFF));
}
}
private static void filterAvg2(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = (byte)((uncompressed[1] & 0xFF) + ((lastRow[0] & 0xFF) >> 1));
byte p1 = lastRow[1] = (byte)((uncompressed[2] & 0xFF) + ((lastRow[1] & 0xFF) >> 1));
for (int i = 2; i < rowSize; i += 2) {
p0 = lastRow[i] = (byte)(((lastRow[i] & 0xFF) + (p0 & 0xFF) >> 1) + (uncompressed[1 + i] & 0xFF));
p1 = lastRow[i + 1] = (byte)(((lastRow[i + 1] & 0xFF) + (p1 & 0xFF) >> 1) + (uncompressed[i + 2] & 0xFF));
}
}
private static void filterSub3(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = uncompressed[1];
byte p1 = lastRow[1] = uncompressed[2];
byte p2 = lastRow[2] = uncompressed[3];
for (int i = 3; i < rowSize; i += 3) {
p0 = lastRow[i] = (byte)((p0 & 0xFF) + (uncompressed[i + 1] & 0xFF));
p1 = lastRow[i + 1] = (byte)((p1 & 0xFF) + (uncompressed[i + 2] & 0xFF));
p2 = lastRow[i + 2] = (byte)((p2 & 0xFF) + (uncompressed[i + 3] & 0xFF));
}
}
private static void filterAvg3(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = (byte)((uncompressed[1] & 0xFF) + ((lastRow[0] & 0xFF) >> 1));
byte p1 = lastRow[1] = (byte)((uncompressed[2] & 0xFF) + ((lastRow[1] & 0xFF) >> 1));
byte p2 = lastRow[2] = (byte)((uncompressed[3] & 0xFF) + ((lastRow[2] & 0xFF) >> 1));
for (int i = 3; i < rowSize; i += 3) {
p0 = lastRow[i] = (byte)(((lastRow[i] & 0xFF) + (p0 & 0xFF) >> 1) + (uncompressed[i + 1] & 0xFF));
p1 = lastRow[i + 1] = (byte)(((lastRow[i + 1] & 0xFF) + (p1 & 0xFF) >> 1) + (uncompressed[i + 2] & 0xFF));
p2 = lastRow[i + 2] = (byte)(((lastRow[i + 2] & 0xFF) + (p2 & 0xFF) >> 1) + (uncompressed[i + 3] & 0xFF));
}
}
private static void filterSub4(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = uncompressed[1];
byte p1 = lastRow[1] = uncompressed[2];
byte p2 = lastRow[2] = uncompressed[3];
byte p3 = lastRow[3] = uncompressed[4];
for (int i = 4; i < rowSize; i += 4) {
p0 = lastRow[i] = (byte)((p0 & 0xFF) + (uncompressed[i + 1] & 0xFF));
p1 = lastRow[i + 1] = (byte)((p1 & 0xFF) + (uncompressed[i + 2] & 0xFF));
p2 = lastRow[i + 2] = (byte)((p2 & 0xFF) + (uncompressed[i + 3] & 0xFF));
p3 = lastRow[i + 3] = (byte)((p3 & 0xFF) + (uncompressed[i + 4] & 0xFF));
}
}
private static void filterAvg4(byte[] uncompressed, byte[] lastRow, int rowSize) {
byte p0 = lastRow[0] = (byte)((uncompressed[1] & 0xFF) + ((lastRow[0] & 0xFF) >> 1));
byte p1 = lastRow[1] = (byte)((uncompressed[2] & 0xFF) + ((lastRow[1] & 0xFF) >> 1));
byte p2 = lastRow[2] = (byte)((uncompressed[3] & 0xFF) + ((lastRow[2] & 0xFF) >> 1));
byte p3 = lastRow[3] = (byte)((uncompressed[4] & 0xFF) + ((lastRow[3] & 0xFF) >> 1));
for (int i = 4; i < rowSize; i += 4) {
p0 = lastRow[i] = (byte)(((lastRow[i] & 0xFF) + (p0 & 0xFF) >> 1) + (uncompressed[i + 1] & 0xFF));
p1 = lastRow[i + 1] = (byte)(((lastRow[i + 1] & 0xFF) + (p1 & 0xFF) >> 1) + (uncompressed[i + 2] & 0xFF));
p2 = lastRow[i + 2] = (byte)(((lastRow[i + 2] & 0xFF) + (p2 & 0xFF) >> 1) + (uncompressed[i + 3] & 0xFF));
p3 = lastRow[i + 3] = (byte)(((lastRow[i + 3] & 0xFF) + (p3 & 0xFF) >> 1) + (uncompressed[i + 4] & 0xFF));
}
}
private static class PLTE {
int[] palette;
public void parse(ByteBuffer data, int length) {
if (length % 3 != 0 || length > 768)
throw new RuntimeException("Invalid data");
int n = length / 3;
this.palette = new int[n];
int i;
for (i = 0; i < n; i++)
this.palette[i] = 0xFF000000 | (data.get() & 0xFF) << 16 | (data.get() & 0xFF) << 8 |
data.get() & 0xFF;
for (; i < 256; i++)
this.palette[i] = -16777216;
data.getInt();
}
}
public static class TRNS {
private int colorType;
byte[] alphaPal;
byte alphaGrey;
byte alphaR;
byte alphaG;
byte alphaB;
TRNS(byte colorType) {
this.colorType = colorType;
}
public void parse(ByteBuffer data, int length) {
if (this.colorType == 3) {
this.alphaPal = new byte[256];
data.get(this.alphaPal, 0, length);
for (int i = length; i < 256; i++)
this.alphaPal[i] = -1;
} else if (this.colorType == 0) {
this.alphaGrey = data.get();
} else if (this.colorType == 2) {
this.alphaR = data.get();
this.alphaG = data.get();
this.alphaG = data.get();
}
data.getInt();
}
}
public VideoCodecMeta getCodecMeta(ByteBuffer _data) {
ByteBuffer data = _data.duplicate();
if (!ispng(data))
throw new RuntimeException("Not a PNG file.");
while (data.remaining() >= 8) {
IHDR ihdr;
int length = data.getInt();
int tag = data.getInt();
if (data.remaining() < length)
break;
switch (tag) {
case 1229472850:
ihdr = new IHDR();
ihdr.parse(data);
return VideoCodecMeta.createSimpleVideoCodecMeta(new Size(ihdr.width, ihdr.height), ColorSpace.RGB);
}
data.position(data.position() + length + 4);
}
return null;
}
private static boolean ispng(ByteBuffer data) {
int sighi = data.getInt();
int siglo = data.getInt();
boolean ispng = ((sighi == -1991225785 || sighi == -1974645177) && (siglo == 218765834 || siglo == 218765834));
return ispng;
}
public static int probe(ByteBuffer data) {
if (!ispng(data))
return 100;
return 0;
}
public static byte[] deflate(byte[] data, Inflater inflater) throws DataFormatException {
inflater.setInput(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length);
byte[] buffer = new byte[16384];
while (!inflater.needsInput()) {
int count = inflater.inflate(buffer);
baos.write(buffer, 0, count);
System.out.println(baos.size());
}
return baos.toByteArray();
}
}

View file

@ -0,0 +1,88 @@
package org.jcodec.codecs.png;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import org.jcodec.common.VideoEncoder;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public class PNGEncoder extends VideoEncoder {
private static int crc32(ByteBuffer from, ByteBuffer to) {
from.limit(to.position());
CRC32 crc32 = new CRC32();
crc32.update(NIOUtils.toArray(from));
return (int)crc32.getValue();
}
public VideoEncoder.EncodedFrame encodeFrame(Picture pic, ByteBuffer out) {
ByteBuffer _out = out.duplicate();
_out.putInt(-1991225785);
_out.putInt(218765834);
IHDR ihdr = new IHDR();
ihdr.width = pic.getCroppedWidth();
ihdr.height = pic.getCroppedHeight();
ihdr.bitDepth = 8;
ihdr.colorType = 2;
_out.putInt(13);
ByteBuffer crcFrom = _out.duplicate();
_out.putInt(1229472850);
ihdr.write(_out);
_out.putInt(crc32(crcFrom, _out));
Deflater deflater = new Deflater();
byte[] rowData = new byte[pic.getCroppedWidth() * 3 + 1];
byte[] pix = pic.getPlaneData(0);
byte[] buffer = new byte[32768];
int ptr = 0, len = buffer.length;
int lineStep = (pic.getWidth() - pic.getCroppedWidth()) * 3;
for (int row = 0, bptr = 0; row < pic.getCroppedHeight() + 1; row++) {
int count;
while ((count = deflater.deflate(buffer, ptr, len)) > 0) {
ptr += count;
len -= count;
if (len == 0) {
_out.putInt(ptr);
crcFrom = _out.duplicate();
_out.putInt(1229209940);
_out.put(buffer, 0, ptr);
_out.putInt(crc32(crcFrom, _out));
ptr = 0;
len = buffer.length;
}
}
if (row >= pic.getCroppedHeight())
break;
rowData[0] = 0;
for (int i = 1; i <= pic.getCroppedWidth() * 3; i += 3, bptr += 3) {
rowData[i] = (byte)(pix[bptr] + 128);
rowData[i + 1] = (byte)(pix[bptr + 1] + 128);
rowData[i + 2] = (byte)(pix[bptr + 2] + 128);
}
bptr += lineStep;
deflater.setInput(rowData);
if (row >= pic.getCroppedHeight() - 1)
deflater.finish();
}
if (ptr > 0) {
_out.putInt(ptr);
crcFrom = _out.duplicate();
_out.putInt(1229209940);
_out.put(buffer, 0, ptr);
_out.putInt(crc32(crcFrom, _out));
}
_out.putInt(0);
_out.putInt(1229278788);
_out.putInt(-1371381630);
_out.flip();
return new VideoEncoder.EncodedFrame(_out, true);
}
public ColorSpace[] getSupportedColorSpaces() {
return new ColorSpace[] { ColorSpace.RGB };
}
public int estimateBufferSize(Picture frame) {
return frame.getCroppedWidth() * frame.getCroppedHeight() * 4;
}
}