packages feed

JuicyPixels-blp-0.3.0.0: src/Codec/Picture/Blp/Internal/Convert.hs

module Codec.Picture.Blp.Internal.Convert(
    fromBlpJpeg
  , jpegAlphaIsUsable
  , toBlpCMYK8
  ) where

import Codec.Picture
import Codec.Picture.Types (pixelFold)
import Data.Word

-- | Convert a decoded mipmap of a JPEG compressed BLP1 into an image that can
-- be written out directly.
--
-- The four components of a BLP1 JPEG are the raw B, G, R and A planes of the
-- texture. The stream carries no Adobe marker, so a JPEG decoder has nothing
-- to go on and reports it as CMYK; running the generic CMYK conversion over
-- it applies an ink model to data that is not ink, and multiplies every
-- colour channel by the alpha plane. Map the planes straight across instead.
--
-- The 'Bool' says whether the alpha plane should be honoured; see
-- 'jpegAlphaIsUsable'. When it is 'False' the texture is made fully opaque.
-- Any other kind of JPEG (greyscale, YCbCr) is converted to RGBA as usual.
fromBlpJpeg :: Bool -> DynamicImage -> DynamicImage
fromBlpJpeg useAlpha i = case i of
  ImageCMYK8 p ->
    let rgba = blpJpegToRGBA8 p
    in ImageRGBA8 $ if useAlpha then rgba else forceOpaque rgba
  _ -> ImageRGBA8 $ convertRGBA8 i

-- | Whether the alpha plane of a decoded BLP1 JPEG mipmap carries information.
--
-- Warcraft III authoring tools disagree about the alpha plane of a JPEG
-- compressed BLP1. Some set the alpha flag and fill the plane with 0x00,
-- others with 0xFF, and both mean the same thing: the texture is opaque and
-- the plane was never populated. Taking either at face value gives a wrong
-- answer for half the files in the wild, so a plane that is entirely 0x00 or
-- entirely 0xFF is treated as carrying no information at all.
--
-- Any other plane, including one that is uniformly semi-transparent, is used
-- as it stands.
jpegAlphaIsUsable :: DynamicImage -> Bool
jpegAlphaIsUsable i = case i of
  ImageCMYK8 p -> case uniformAlpha (blpJpegToRGBA8 p) of
    Just a -> a /= 0 && a /= 255
    Nothing -> True
  _ -> True

-- | The alpha value every pixel of an image carries, if they all agree. An
-- empty image counts as uniformly opaque.
uniformAlpha :: Image PixelRGBA8 -> Maybe Word8
uniformAlpha img
  | imageWidth img <= 0 || imageHeight img <= 0 = Just 255
  | pixelFold same True img = Just a0
  | otherwise = Nothing
  where
    PixelRGBA8 _ _ _ a0 = pixelAt img 0 0
    same acc _ _ (PixelRGBA8 _ _ _ a) = acc && a == a0

-- | Reinterpret the four planes a JPEG decoder labelled CMYK as the BGRA they
-- actually are. This inverts 'toBlpCMYK8'.
blpJpegToRGBA8 :: Image PixelCMYK8 -> Image PixelRGBA8
blpJpegToRGBA8 = pixelMap convert
  where
  convert (PixelCMYK8 c m y k) = PixelRGBA8 y m c k

forceOpaque :: Image PixelRGBA8 -> Image PixelRGBA8
forceOpaque = pixelMap convert
  where
  convert (PixelRGBA8 r g b _) = PixelRGBA8 r g b 255

-- | Lay out the B, G, R and A planes of a texture as the four components of
-- a BLP1 JPEG. The encoder labels them CMYK only because that is the
-- four-component JPEG it can write; this is the exact inverse of
-- @blpJpegToRGBA8@.
toBlpCMYK8 :: Image PixelRGBA8 -> Image PixelCMYK8
toBlpCMYK8 = pixelMap convert
  where
  convert (PixelRGBA8 r g b a) = PixelCMYK8 b g r a