import 'dart:typed_data'; | |
import '../../color.dart'; | |
import '../../image.dart'; | |
import '../../util/input_buffer.dart'; | |
import 'pvrtc_color.dart'; | |
import 'pvrtc_packet.dart'; | |
/// Ported from Jeffrey Lim's PVRTC encoder/decoder, | |
/// https://bitbucket.org/jthlim/pvrtccompressor | |
class PvrtcDecoder { | |
Image decodePvr(List<int> data) { | |
// Use a heuristic to detect potential apple PVRTC formats | |
if (_countBits(data.length) == 1) { | |
// very likely to be apple PVRTC | |
var image = decodeApplePVRTC(data); | |
if (image != null) { | |
return image; | |
} | |
} | |
var input = InputBuffer(data, bigEndian: false); | |
var magic = input.readUint32(); | |
if (magic == 0x03525650) { | |
return decodePVR3(data); | |
} | |
return decodePVR2(data); | |
} | |
Image decodeApplePVRTC(List<int> data) { | |
// additional heuristic | |
const int HEADER_SIZE = 52; | |
if (data.length > HEADER_SIZE) { | |
InputBuffer input = InputBuffer(data, bigEndian: false); | |
// Header | |
int size = input.readUint32(); | |
if (size == HEADER_SIZE) { | |
return null; | |
} | |
/*int height =*/ input.readUint32(); | |
/*int width =*/ input.readUint32(); | |
/*int mipcount =*/ input.readUint32(); | |
/*int flags =*/ input.readUint32(); | |
/*int texdatasize =*/ input.readUint32(); | |
/*int bpp =*/ input.readUint32(); | |
/*int rmask =*/ input.readUint32(); | |
/*int gmask =*/ input.readUint32(); | |
/*int bmask =*/ input.readUint32(); | |
int magic = input.readUint32(); | |
if (magic == 0x21525650) { | |
// this looks more like a PowerVR file. | |
return null; | |
} | |
} | |
//const int PVRTC2 = 1; | |
//const int PVRTC4 = 2; | |
int mode = 1; | |
int res = 8; | |
int size = data.length; | |
//int format = 0; | |
// this is a tough one, could be 2bpp 8x8, 4bpp 8x8 | |
if (size == 32) { | |
// assume 4bpp, 8x8 | |
mode = 0; | |
res = 8; | |
} else { | |
// Detect if it's 2bpp or 4bpp | |
int shift = 0; | |
int test2bpp = 0x40; // 16x16 | |
int test4bpp = 0x80; // 16x16 | |
while (shift < 10) { | |
int s2 = shift << 1; | |
if ((test2bpp << s2) & size != 0) { | |
res = 16 << shift; | |
mode = 1; | |
//format = PVRTC2; | |
break; | |
} | |
if ((test4bpp << s2) & size != 0) { | |
res = 16 << shift; | |
mode = 0; | |
//format = PVRTC4; | |
break; | |
} | |
++shift; | |
} | |
if (shift == 10) { | |
// no mode could be found. | |
return null; | |
} | |
} | |
// there is no reliable way to know if it's a 2bpp or 4bpp file. Assuming | |
int width = res; | |
int height = res; | |
int bpp = (mode + 1) * 2; | |
//int numMips = 0; | |
if (bpp == 4) { | |
// 2bpp is currently unsupported | |
return null; | |
} | |
return decodeRgba4bpp(width, height, data as TypedData); | |
} | |
Image decodePVR2(List<int> data) { | |
int length = data.length; | |
const int HEADER_SIZE = 52; | |
const int PVRTEX_CUBEMAP = (1 << 12); | |
const int PVR_PIXELTYPE_MASK = 0xff; | |
const int PVR_TYPE_RGBA4444 = 0x10; | |
const int PVR_TYPE_RGBA5551 = 0x11; | |
const int PVR_TYPE_RGBA8888 = 0x12; | |
const int PVR_TYPE_RGB565 = 0x13; | |
const int PVR_TYPE_RGB555 = 0x14; | |
const int PVR_TYPE_RGB888 = 0x15; | |
const int PVR_TYPE_I8 = 0x16; | |
const int PVR_TYPE_AI8 = 0x17; | |
const int PVR_TYPE_PVRTC2 = 0x18; | |
const int PVR_TYPE_PVRTC4 = 0x19; | |
if (length < HEADER_SIZE) { | |
return null; | |
} | |
InputBuffer input = InputBuffer(data, bigEndian: false); | |
// Header | |
int size = input.readUint32(); | |
int height = input.readUint32(); | |
int width = input.readUint32(); | |
/*int mipcount =*/ input.readUint32(); | |
int flags = input.readUint32(); | |
/*int texdatasize =*/ input.readUint32(); | |
int bpp = input.readUint32(); | |
/*int rmask =*/ input.readUint32(); | |
/*int gmask =*/ input.readUint32(); | |
/*int bmask =*/ input.readUint32(); | |
int amask = input.readUint32(); | |
int magic = input.readUint32(); | |
int numtex = input.readUint32(); | |
if (size != HEADER_SIZE || magic != 0x21525650) { | |
return null; | |
} | |
if (numtex < 1) { | |
numtex = (flags & PVRTEX_CUBEMAP) != 0 ? 6 : 1; | |
} | |
if (numtex != 1) { | |
// only 1 surface supported currently | |
return null; | |
} | |
if (width * height * bpp / 8 > length - HEADER_SIZE) { | |
return null; | |
} | |
int ptype = flags & PVR_PIXELTYPE_MASK; | |
switch (ptype) { | |
case PVR_TYPE_RGBA4444: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int v1 = input.readByte(); | |
int v2 = input.readByte(); | |
int a = (v1 & 0x0f) << 4; | |
int b = (v1 & 0xf0); | |
int g = (v2 & 0x0f) << 4; | |
int r = (v2 & 0xf0); | |
out[oi++] = r; | |
out[oi++] = g; | |
out[oi++] = b; | |
out[oi++] = a; | |
} | |
} | |
return image; | |
case PVR_TYPE_RGBA5551: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int v = input.readUint16(); | |
int r = (v & 0xf800) >> 8; | |
int g = (v & 0x07c0) >> 3; | |
int b = (v & 0x003e) << 2; | |
int a = (v & 0x0001) != 0 ? 255 : 0; | |
out[oi++] = r; | |
out[oi++] = g; | |
out[oi++] = b; | |
out[oi++] = a; | |
} | |
} | |
return image; | |
case PVR_TYPE_RGBA8888: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
out[oi++] = input.readByte(); | |
out[oi++] = input.readByte(); | |
out[oi++] = input.readByte(); | |
out[oi++] = input.readByte(); | |
} | |
} | |
return image; | |
case PVR_TYPE_RGB565: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int v = input.readUint16(); | |
int b = (v & 0x001f) << 3; | |
int g = (v & 0x07e0) >> 3; | |
int r = (v & 0xf800) >> 8; | |
int a = 255; | |
out[oi++] = r; | |
out[oi++] = g; | |
out[oi++] = b; | |
out[oi++] = a; | |
} | |
} | |
return image; | |
case PVR_TYPE_RGB555: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int v = input.readUint16(); | |
int r = (v & 0x001f) << 3; | |
int g = (v & 0x03e0) >> 2; | |
int b = (v & 0x7c00) >> 7; | |
int a = 255; | |
out[oi++] = r; | |
out[oi++] = g; | |
out[oi++] = b; | |
out[oi++] = a; | |
} | |
} | |
return image; | |
case PVR_TYPE_RGB888: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
out[oi++] = input.readByte(); | |
out[oi++] = input.readByte(); | |
out[oi++] = input.readByte(); | |
out[oi++] = 255; | |
} | |
} | |
return image; | |
case PVR_TYPE_I8: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int i = input.readByte(); | |
out[oi++] = i; | |
out[oi++] = i; | |
out[oi++] = i; | |
out[oi++] = 255; | |
} | |
} | |
return image; | |
case PVR_TYPE_AI8: | |
Image image = Image(width, height); | |
Uint8List out = image.getBytes(); | |
int oi = 0; | |
for (int y = 0; y < height; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int i = input.readByte(); | |
int a = input.readByte(); | |
out[oi++] = i; | |
out[oi++] = i; | |
out[oi++] = i; | |
out[oi++] = a; | |
} | |
} | |
return image; | |
case PVR_TYPE_PVRTC2: | |
// Currently unsupported | |
return null; | |
case PVR_TYPE_PVRTC4: | |
return amask == 0 | |
? decodeRgb4bpp(width, height, input.toUint8List()) | |
: decodeRgba4bpp(width, height, input.toUint8List()); | |
} | |
// Unknown format | |
return null; | |
} | |
Image decodePVR3(List<int> data) { | |
//const int PVR3_PVRTC_2BPP_RGB = 0; | |
//const int PVR3_PVRTC_2BPP_RGBA = 1; | |
const int PVR3_PVRTC_4BPP_RGB = 2; | |
const int PVR3_PVRTC_4BPP_RGBA = 3; | |
/*const int PVR3_PVRTC2_2BPP = 4; | |
const int PVR3_PVRTC2_4BPP = 5; | |
const int PVR3_ETC1 = 6; | |
const int PVR3_DXT1 = 7; | |
const int PVR3_DXT2 = 8; | |
const int PVR3_DXT3 = 9; | |
const int PVR3_DXT4 = 10; | |
const int PVR3_DXT5 = 11; | |
const int PVR3_BC1 = 7; | |
const int PVR3_BC2 = 9; | |
const int PVR3_BC3 = 11; | |
const int PVR3_BC4 = 12; | |
const int PVR3_BC5 = 13; | |
const int PVR3_BC6 = 14; | |
const int PVR3_BC7 = 15; | |
const int PVR3_UYVY = 16; | |
const int PVR3_YUY2 = 17; | |
const int PVR3_BW_1BPP = 18; | |
const int PVR3_R9G9B9E5 = 19; | |
const int PVR3_RGBG8888 = 20; | |
const int PVR3_GRGB8888 = 21; | |
const int PVR3_ETC2_RGB = 22; | |
const int PVR3_ETC2_RGBA = 23; | |
const int PVR3_ETC2_RGB_A1 = 24; | |
const int PVR3_EAC_R11_U = 25; | |
const int PVR3_EAC_R11_S = 26; | |
const int PVR3_EAC_RG11_U = 27; | |
const int PVR3_EAC_RG11_S = 28;*/ | |
InputBuffer input = InputBuffer(data); | |
// Header | |
int version = input.readUint32(); | |
if (version != 0x03525650) { | |
return null; | |
} | |
/*int flags =*/ input.readUint32(); | |
var format = input.readUint32(); | |
var order = [ | |
input.readByte(), | |
input.readByte(), | |
input.readByte(), | |
input.readByte() | |
]; | |
/*int colorspace =*/ input.readUint32(); | |
/*int channeltype =*/ input.readUint32(); | |
int height = input.readUint32(); | |
int width = input.readUint32(); | |
/*int depth =*/ input.readUint32(); | |
/*int num_surfaces =*/ input.readUint32(); | |
/*int num_faces =*/ input.readUint32(); | |
/*int mipcount =*/ input.readUint32(); | |
int metadata_size = input.readUint32(); | |
input.skip(metadata_size); | |
if (order[0] == 0) { | |
switch (format) { | |
case PVR3_PVRTC_4BPP_RGB: | |
return decodeRgb4bpp(width, height, input.toUint8List()); | |
case PVR3_PVRTC_4BPP_RGBA: | |
return decodeRgba4bpp(width, height, input.toUint8List()); | |
/*case PVR3_PVRTC_2BPP_RGB: | |
return null; | |
case PVR3_PVRTC_2BPP_RGBA: | |
return null; | |
case PVR3_PVRTC2_2BPP: | |
return null; | |
case PVR3_PVRTC2_4BPP: | |
return null; | |
case PVR3_ETC1: | |
return null; | |
case PVR3_DXT1: | |
return null; | |
case PVR3_DXT2: | |
return null; | |
case PVR3_DXT3: | |
return null; | |
case PVR3_DXT4: | |
return null; | |
case PVR3_DXT5: | |
return null; | |
case PVR3_BC1: | |
return null; | |
case PVR3_BC2: | |
return null; | |
case PVR3_BC3: | |
return null; | |
case PVR3_BC4: | |
return null; | |
case PVR3_BC5: | |
return null; | |
case PVR3_BC6: | |
return null; | |
case PVR3_BC7: | |
return null; | |
case PVR3_UYVY: | |
return null; | |
case PVR3_YUY2: | |
return null; | |
case PVR3_BW_1BPP: | |
return null; | |
case PVR3_R9G9B9E5: | |
return null; | |
case PVR3_RGBG8888: | |
return null; | |
case PVR3_GRGB8888: | |
return null; | |
case PVR3_ETC2_RGB: | |
return null; | |
case PVR3_ETC2_RGBA: | |
return null; | |
case PVR3_ETC2_RGB_A1: | |
return null; | |
case PVR3_EAC_R11_U: | |
return null; | |
case PVR3_EAC_R11_S: | |
return null; | |
case PVR3_EAC_RG11_U: | |
return null; | |
case PVR3_EAC_RG11_S: | |
return null;*/ | |
} | |
} | |
return null; | |
} | |
int _countBits(int x) { | |
x = (x - ((x >> 1) & 0x55555555)) & 0xffffffff; | |
x = ((x & 0x33333333) + ((x >> 2) & 0x33333333)) & 0xffffffff; | |
x = (x + (x >> 4)) & 0xffffffff; | |
x &= 0xf0f0f0f; | |
x = ((x * 0x01010101) & 0xffffffff) >> 24; | |
return x; | |
} | |
Image decodeRgb4bpp(int width, int height, TypedData data) { | |
var result = Image(width, height, Image.RGB); | |
final int blocks = width ~/ 4; | |
final int blockMask = blocks - 1; | |
final packet = PvrtcPacket(data); | |
final p0 = PvrtcPacket(data); | |
final p1 = PvrtcPacket(data); | |
final p2 = PvrtcPacket(data); | |
final p3 = PvrtcPacket(data); | |
final c = PvrtcColorRgb(); | |
const factors = PvrtcPacket.BILINEAR_FACTORS; | |
const weights = PvrtcPacket.WEIGHTS; | |
for (int y = 0; y < blocks; ++y) { | |
for (int x = 0; x < blocks; ++x) { | |
packet.setBlock(x, y); | |
int mod = packet.modulationData; | |
int weightIndex = 4 * packet.usePunchthroughAlpha; | |
int factorIndex = 0; | |
for (int py = 0; py < 4; ++py) { | |
int yOffset = (py < 2) ? -1 : 0; | |
int y0 = (y + yOffset) & blockMask; | |
int y1 = (y0 + 1) & blockMask; | |
int pyi = (py + y * 4) * width; | |
for (int px = 0; px < 4; ++px) { | |
int xOffset = (px < 2) ? -1 : 0; | |
int x0 = (x + xOffset) & blockMask; | |
int x1 = (x0 + 1) & blockMask; | |
p0.setBlock(x0, y0); | |
p1.setBlock(x1, y0); | |
p2.setBlock(x0, y1); | |
p3.setBlock(x1, y1); | |
var ca = p0.getColorRgbA() * factors[factorIndex][0] + | |
p1.getColorRgbA() * factors[factorIndex][1] + | |
p2.getColorRgbA() * factors[factorIndex][2] + | |
p3.getColorRgbA() * factors[factorIndex][3]; | |
var cb = p0.getColorRgbB() * factors[factorIndex][0] + | |
p1.getColorRgbB() * factors[factorIndex][1] + | |
p2.getColorRgbB() * factors[factorIndex][2] + | |
p3.getColorRgbB() * factors[factorIndex][3]; | |
var w = weights[weightIndex + mod & 3]; | |
c.r = (ca.r * w[0] + cb.r * w[1]) >> 7; | |
c.g = (ca.g * w[0] + cb.g * w[1]) >> 7; | |
c.b = (ca.b * w[0] + cb.b * w[1]) >> 7; | |
int pi = (pyi + (px + x * 4)); | |
result[pi] = getColor(c.r, c.g, c.b, 255); | |
mod >>= 2; | |
factorIndex++; | |
} | |
} | |
} | |
} | |
return result; | |
} | |
Image decodeRgba4bpp(int width, int height, TypedData data) { | |
var result = Image(width, height, Image.RGBA); | |
final int blocks = width ~/ 4; | |
final int blockMask = blocks - 1; | |
final packet = PvrtcPacket(data); | |
final p0 = PvrtcPacket(data); | |
final p1 = PvrtcPacket(data); | |
final p2 = PvrtcPacket(data); | |
final p3 = PvrtcPacket(data); | |
final c = PvrtcColorRgba(); | |
const factors = PvrtcPacket.BILINEAR_FACTORS; | |
const weights = PvrtcPacket.WEIGHTS; | |
for (int y = 0; y < blocks; ++y) { | |
for (int x = 0; x < blocks; ++x) { | |
packet.setBlock(x, y); | |
int mod = packet.modulationData; | |
int weightIndex = 4 * packet.usePunchthroughAlpha; | |
int factorIndex = 0; | |
for (int py = 0; py < 4; ++py) { | |
int yOffset = (py < 2) ? -1 : 0; | |
int y0 = (y + yOffset) & blockMask; | |
int y1 = (y0 + 1) & blockMask; | |
int pyi = (py + y * 4) * width; | |
for (int px = 0; px < 4; ++px) { | |
int xOffset = (px < 2) ? -1 : 0; | |
int x0 = (x + xOffset) & blockMask; | |
int x1 = (x0 + 1) & blockMask; | |
p0.setBlock(x0, y0); | |
p1.setBlock(x1, y0); | |
p2.setBlock(x0, y1); | |
p3.setBlock(x1, y1); | |
var ca = p0.getColorRgbaA() * factors[factorIndex][0] + | |
p1.getColorRgbaA() * factors[factorIndex][1] + | |
p2.getColorRgbaA() * factors[factorIndex][2] + | |
p3.getColorRgbaA() * factors[factorIndex][3]; | |
var cb = p0.getColorRgbaB() * factors[factorIndex][0] + | |
p1.getColorRgbaB() * factors[factorIndex][1] + | |
p2.getColorRgbaB() * factors[factorIndex][2] + | |
p3.getColorRgbaB() * factors[factorIndex][3]; | |
var w = weights[weightIndex + mod & 3]; | |
c.r = (ca.r * w[0] + cb.r * w[1]) >> 7; | |
c.g = (ca.g * w[0] + cb.g * w[1]) >> 7; | |
c.b = (ca.b * w[0] + cb.b * w[1]) >> 7; | |
c.a = (ca.a * w[2] + cb.a * w[3]) >> 7; | |
int pi = (pyi + (px + x * 4)); | |
result[pi] = getColor(c.r, c.g, c.b, c.a); | |
mod >>= 2; | |
factorIndex++; | |
} | |
} | |
} | |
} | |
return result; | |
} | |
} |