Repository navigation
Releases: NCrashed/JuicyPixels-blp
Release list
JuicyPixels-blp 1.0.0.0
The library now reads and writes both BLP versions in use, and its API is
meant to stay. Decoding reports why it fails and returns the image type it
always produced, BLP2 can be written, and the deprecated encoders are gone.
Every decoding function and the encoding options change; see below.
Writing BLP2
- Breaking:
BlpEncodeOptionshas a new field,blpVersion(Blp1by
default), andBlpFormatnew constructorsBlpFormatDxt1,
BlpFormatDxt3,BlpFormatDxt5andBlpFormatBgra. These exist only in
BLP2; JPEG exists only in BLP1, and a palette in both. Other combinations
give the newBlpUnsupportedFormat. - The DXT encoder fits each block's colours along their principal axis,
refines the endpoints by least squares, and tries a single-colour fit
from exact tables. Against ImageMagick's DDS encoders on seven test
images it is 0.7–2.5 dB above range fit and 0.2–0.6 dB below cluster
fit (1.8 dB on a 64×64 icon). It encodes a 512×512 texture in about
80 ms (DXT1) or 100 ms (DXT5). - Files written this way decode with an independent decoder (Pinta365/blp,
with the DXT5 alpha fix reported in Pinta365/blp#1): palette and BGRA
exactly, DXT within the two decoders' rounding differences. blp2any convertwrites BLP2 with-b dxt1,dxt3,dxt5orbgra,
and paletted BLP2 with--blp2.
Decoding
- Breaking:
decodeBlp,decodeBlpWith,readBlpand theMipmaps
variants returnImage PixelRGBA8instead ofDynamicImage; the image
was alwaysImageRGBA8. Drop theconvertRGBA8call, or wrap the result
inImageRGBA8where aDynamicImageis needed. - Breaking: they and
blpInforeport errors asBlpDecodeError
instead ofString:BlpNotBlp,BlpTruncatedHeader,BlpUnsupported,
BlpInvalidSize,BlpTooLarge,BlpNoImageDataandBlpCorruptImage.
displayBlpDecodeErrorgives a readable message. Callers can tell a
broken file from one that is too large or of an unsupported kind without
parsing text. - A paletted file without any usable mip level is now
BlpNoImageDatafor
decodeBlpMipmapstoo; it used to give an empty list. JPEG files already
failed this way. Apart from that, decoding is unchanged: an equivalence
run against 0.6.0.0 over 4735 cases gives the same pixels, levels,
blpInforesults and failures. - JPEG errors from JuicyPixels name the level, for example
JPEG mipmap 0: not enough bytes.
Also
- Breaking: the deprecated
encodeBlpJpeg,
encodeBlpUncompressedWithAlpha,encodeBlpUncompressedWithoutAlphaand
theirwriteBlp*variants are removed. UseencodeBlpWithor
writeBlpWith; unlike them, these returnLeftinstead of throwing. BlpDecodeErrorandBlpEncodeErrorareExceptions, and the public
result and option types haveNFDatainstances.blp2any statsgroups BLP2 files too, by content type,alphaDepth,
alphaTypeandhasMips. Group names of BLP1 files are unchanged.- The README lists what is out of scope on purpose: BLP0, BLP2 with JPEG
content, chosen header values through the public encoder, and archives
or other texture formats. - Internal modules:
Internal.DxthasencodeDxt.Internal.EncoderhastoBlp2Struct,encodeBlp2andblp2Encoder,
andtoBlpStructreturnsEither BlpEncodeError.- The parser and decoder return
BlpDecodeError, and the decoder
Image PixelRGBA8. - The BLP1 magic parser
blpVersionis renamedblp1Tag, as the name
now belongs to the option.
JuicyPixels-blp 0.6.0.0
BLP2, the World of Warcraft variant, can be read. BlpInfo and
BlpCompression change for it; see the breaking change below.
decodeBlp,decodeBlpMipmaps, theirWithandreadvariants and
blpInforead BLP2 files: DXT1, DXT3, DXT5, BGRA and paletted content,
with mipmaps (#3). Damaged levels are handled as in BLP1, and the size
limit applies. An alpha depth of 0 makes the image opaque. BLP2 files
with JPEG content (type0) are rejected; no known file uses them.
blp2any convertreads BLP2 too.- Decoding was checked pixel by pixel against the BLP2 sample files of
Pillow and of the Pinta365/blp library. BGRA and paletted content match
exactly. DXT colours differ by at most 2 per channel from Pinta365/blp,
from rounding, and by up to 6 from Pillow, whose BLP plugin skips the
bit replication of RGB565 endpoints. Seedocs/format.md. - Breaking:
BlpInfohas a new first field,blpInfoVersion :: BlpVersion(Blp1orBlp2), andBlpCompressionnew constructors
BlpCompressionDxt1,BlpCompressionDxt3,BlpCompressionDxt5and
BlpCompressionBgra. Code that buildsBlpInfopositionally or matches
BlpCompressionexhaustively needs updating. - BLP1 decoding is unchanged, and files whose magic is neither
BLP1nor
BLP2are still rejected with the BLP1 parser's error. - The option, result and error types of
Codec.Picture.Blpare now defined
outside the internal modules, so changes to the internal file structure
cannot change them.Codec.Picture.Blp.Internal.Datastill re-exports
the ones it exported before. - The benchmarks accept
tasty-bench0.5. 0.5.0.0 has the same bound in a
Hackage revision. - Internal modules:
- New
Codec.Picture.Blp.Internal.Dxt, a DXT1, DXT3 and DXT5 block
decoder (decodeDxt,dxtLevelBytes). - New
Codec.Picture.Blp.Internal.Decoder, which turns a parsed file
into images:decodeBlpFile,decodeBlpStruct,decodeBlp2Struct
and the matching...Infofunctions. Internal.Data:Blp2Struct,Blp2Content,BlpFile, and
mipChainLength(moved fromInternal.Parser, which still re-exports
it) next to the newmipLevelsDownTo.Internal.Parser:parseBlp2,parseBlpFileandreadPalette.
- New
JuicyPixels-blp 0.5.0.0
Paletted textures look better, and encoding takes new options. The
encoder's output changes, and BlpEncodeOptions has new fields.
- Paletted mipmaps after the first are averaged and mapped to the nearest
palette colour, instead of taking one pixel of the dithered image per
block. Measured against exact downscaling, levels 1 to 5 gain 8–17 dB on a
smooth gradient, about 11 dB on a noisy texture (15–16 dB to 26–27 dB),
and up to 25 dB on the small levels of a soft-edged sprite. Mip 0 is
unchanged. Paletted encoding is 20–30% slower for it. - Fully transparent pixels count as black when the palette is built, so
colours hidden under them no longer take palette entries. BlpEncodeOptionsgainsblpDither(on by default) andblpMaxSize,
which halves the image until its larger side fits; Warcraft III shows
mip levels up to 512 pixels only.blp2anyhas--no-ditherand
--max-size PIXELS. Build options fromdefaultBlpEncodeOptions; code
that applied theBlpEncodeOptionsconstructor to two arguments needs
updating.BlpInvalidMaxSizeis a newBlpEncodeError.- JPEG textures made from RGBA images whose pixels are all opaque are
written without alpha. - Ready-made
blp2anybinaries for Windows, macOS (Apple Silicon) and
Linux are attached to the GitHub release (#45). Codec.Picture.Blp.Internal.Encoder:toBlpStructtakes
BlpEncodeOptions;shrinkTois new.
JuicyPixels-blp 0.4.1.0
- Decoding refuses images above
blpMaxPixelspixels, 8192x8192 by default,
before allocating anything.decodeBlpWithanddecodeBlpMipmapsWithtake
BlpDecodeOptionsto set the limit. The 0.4.0.0 notes claimed that small
files could no longer demand gigabytes; that was wrong. A JPEG frame
smaller than its level is padded to the level size, and the level size
comes from the header, so a 493-byte file claiming 65535x65535 pixels
still made the decoder allocate about 17 GB. blpInforeads the header (content type, size, alpha bits, usable mip
levels) without decoding any pixels, so callers can reject textures
before decoding them.BlpCompressionis exported fromCodec.Picture.Blp.- blp2any:
--timeout SECONDSgives up on a file that takes too long, so a corrupt
JPEG that makes decoding hang no longer stalls a batch.- An output name with an unknown extension is an error; it used to be
written as PNG regardless, and.tifoutput really was PNG. - It refuses to write over its input, rejects
-qoutside 1..100, prints
encoding errors in words, andstatsfinds*.BLPin upper case.
- Lower bounds now match the oldest versions CI tests: JuicyPixels 3.3.6,
vector 0.12.3, attoparsec 0.14.4, binary 0.8.9, bytestring 0.11.5 and
deepseq 1.4.8 (and for blp2any directory 1.3.7, filepath 1.4.2,
optparse-simple 0.1, transformers 0.5.6), and a CI job builds and tests
with exactly those. The old bounds allowed versions that were never
tested. - Fixed the documentation of
decodeBlpMipmaps, which rendered as "Element
@ is always mip level @".
JuicyPixels-blp 0.4.0.0
Breaking changes:
- New encoding API:
encodeBlpWithandwriteBlpWithtake
BlpEncodeOptions(aBlpFormatand a mip level count) and return
Either BlpEncodeError. Empty images, sides above 65535, JPEG quality
outside 1..100 and files over 4 GiB are errors instead of crashes or
silent wrap-around (#31).encodeBlpJpeg,encodeBlpUncompressedWithAlpha,
encodeBlpUncompressedWithoutAlphaand theirwriteBlp*variants still
work as deprecated wrappers; they clamp the JPEG quality, which used to
wrap modulo 256. - The decoder takes alpha from the
alphaBitsheader field, as the format
specifies, instead of from theextrafield. Paletted files with 1-bit and
4-bit alpha now decode, files whoseextrafield says otherwise keep their
alpha, and unknownextravalues are no longer rejected (#25). - Non-square textures get the full mip chain down to 1x1 instead of
stopping when the shorter side reaches one pixel, andmipMapsUpTocounts
levels by the larger side, capped at 16 (#26, #31). - Mipmap colour is averaged with alpha as the weight, so transparent pixels
no longer darken the edges of sprites, and the alpha of paletted mipmaps
is averaged instead of sampled. Opaque textures encode exactly as before
(#32). Codec.Picture.Blp.Internal.*:BlpStructkeeps the raw header values inblpAlphaBits,blpExtraand
blpHasMipmaps;blpCompressionis a function ofblpExt.BlpFlagand
BlpPictureTypeare gone.- Paletted content is one
BlpPalettedconstructor holding each level's
raw data. - The parser is
parseBlpplusmipLevelsandjpegFrameSize; the
attoparsec walk over the content (blpJpegParser,skipToOffset, ...)
is gone. encodeBlpandblpEncodertake just theBlpStruct;toBlpStruct
takes aBlpFormat.
Decoding untrusted files (#30):
- Errors that JuicyPixels' JPEG decoder raises from pure code are returned
asLeft; in a tail mipmap they end the list. - Width and height must be between 1 and 65535. Larger values used to
overflow the size checks and leave an exception in the decoded image. - Each JPEG level's frame size is read before decoding. A frame larger than
the level is rejected, so an 800-byte file can no longer demand gigabytes;
a smaller one is padded with transparent black. - Mipmap slots are read by index. A missing or garbage slot ends the chain
instead of shifting the later levels, and a garbage slot that points into
the palette no longer breaks the file. - Some corrupt JPEG streams still make JuicyPixels loop forever
(haskell-github-trust/Juicy.Pixels#230), so decoding untrusted files needs
a timeout.
Encoder fixes (#31):
- Unused mipmap slots are zero, as the format requires, and
hasMipmapsis
0 when only one level is written. - Only the levels that are written are encoded; asking for one level of a
2048x2048 JPEG texture used to encode all of them.
Faster (#34): JPEG decoding is 2.1–2.7x and JPEG encoding 2.2–2.8x as fast,
measured with the new benchmark suite (bench/Main.hs). Output is
byte-identical.
blp2any (#33):
-s/--shallowand-p(now also--flatten) were accepted but did
nothing; they now keep conversion to the top folder and write all output
into one folder.- A file that fails no longer aborts a batch conversion or
stats: it is
reported on stderr and skipped, andconvertprints a summary and exits
with status 1 if anything failed. - An input folder given with a trailing slash no longer sends output to the
root of the file system. --versionreports the package version.
Documentation: docs/format.md describes the BLP1 format and what the
library does with each part; the README covers usage, blp2any,
limitations and benchmarking; every module has haddock (#27).
JuicyPixels-blp 0.3.0.0
Breaking changes:
encodeBlpUncompressedWithAlphaalways writes an alpha plane and
encodeBlpUncompressedWithoutAlphanever does. Both used to pick by the
input image, so the two were identical andblp2any -bhad no effect.decodeBlpanddecodeBlpMipmapsalways returnImageRGBA8. JPEG BLPs
whose stream is not four-component (greyscale, YCbCr) used to come back as
ImageY8orImageRGB8.- Removed unused functions from
Codec.Picture.Blp.Internal.Convert:
toPngRepresentableandtoBlpUncompressable. mipMapsUpTocounts every level down to the minimum side; it was one short
and returned 0 or less for tiny images.- Removed the
TextShowandHashableinstances from
Codec.Picture.Blp.Internal.Data, and with them thetext-showand
hashabledependencies.blp2anyno longer depends on
unordered-containers.
The encoder always writes mip 0; a mipmap count of zero or less used to
produce an unreadable file, which blp2any did for 1x1 images.
The uncompressed encoder no longer hands out ByteStrings over image memory it
does not keep alive, and no longer relies on an orphan, endianness-dependent
Storable PixelRGBA8 instance.
Decoder robustness for BLP1 files found in the wild:
- Accept picture type 6 (WoW JPEG variant) and picture type 0 (#5, #10).
- Tolerate palettes shorter than 256 entries (#6).
- Decode files labelled with-alpha whose mipmap 0 has no alpha plane as
without-alpha instead of crashing (#8). - A corrupt JPEG mipmap no longer fails the whole file; the mipmap list is
cut at the first broken level (#9). decodeBlpMipmapsrenders uncompressed mipmaps at their own sizes instead of
crashing; a mip 0 too short for the image is aLeft(#13).- Without-alpha textures decode opaque regardless of the palette's fourth
byte, so the encoder's own output no longer comes back transparent (#14). - Decoding no longer dumps the parsed structure to stderr (#15).
- The JPEG encoder writes plain BGRA planes instead of dividing colour by
alpha, and a uniformly semi-transparent alpha plane is kept; only all-0x00
or all-0xFF planes count as unpopulated (#17). - The JPEG encoder shares one header between mipmaps; 1x1 textures no longer
encode to a file without image data (#18). - Skip mipmap slots that point past the end of the file or overlap an earlier
one (#7). - Decode JPEG planes as BGRA instead of CMYK (#11).
Support GHC 9.4 – 9.14: relax upper bounds for attoparsec, bytestring,
hashable, text-show, vector and filepath. Minimum supported GHC is now 9.4.
First release
- Fully functional BLP1 encoder/decoder
- Results are operational under Warcraft III 1.28
- Windows is supported