packages feed

ascii85x-0.2.4.1: src/Data/TI85/Var/Pic.hs

-- | Picture variable types and utilities.
module Data.TI85.Var.Pic (
    -- * TI Bitmap
    TIBitmap,
    emptyBitmap,
    fromBytes,
    toBytes,
    -- * Display utilities
    showAsciiArt,
    writePicPng
    ) where

import Prelude hiding (take, drop)
import Data.Text (Text, pack, intercalate, take, drop)
import Data.Text.Encoding (decodeLatin1)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Codec.Picture
import Data.Bits
import Data.Word
import Data.Vector.Storable hiding (map,take,drop)
import Numeric (showHex)

import Data.TI85.Encoding (tiDecode)

-- | A TI Picture variable is encoded as a packed bitmap,
-- and is always 128x63 binary pixels.
newtype TIBitmap = TIBitmap ByteString deriving Show

_bmCols = 128
_bmRows = 63
_bmSizeBytes = 1008

-- | A blank (all zero) bitmap
emptyBitmap :: TIBitmap
emptyBitmap = TIBitmap (BS.replicate _bmSizeBytes 0x0)

-- | Create a bitmap from a packed `ByteString`. If the data
-- is the wrong size, returns `Nothing`.
fromBytes :: ByteString -> Maybe TIBitmap
fromBytes bytes = if BS.length bytes == 1008
    then Just (TIBitmap bytes)
    else Nothing

-- | Extract the raw `ByteString` from a bitmap.
toBytes :: TIBitmap -> ByteString
toBytes (TIBitmap bytes) = bytes

-- Expands an 8-bit word into an 8-element ByteString
explodeWord :: Word8 -> ByteString
explodeWord w =
    let (bits,_) = BS.unfoldrN 8 (\x -> Just (x .&. 1, shiftR x 1)) w
    in BS.reverse bits

-- Very approximate calculator-like colors
screenColorMap :: Word8 -> Pixel8
screenColorMap 0x0 = 0xc6
screenColorMap _ = 0x39

-- Convert a `ByteString`, where each bit represents a pixel into
-- a vector of pixels suitable for use in a `DynamicImage`.
-- Bits are mapped to pixels using a vaguely calculator-like
-- color map (light gray background, dark gray foreground).
bytesToPixels :: ByteString -> Vector Pixel8
bytesToPixels =  fromList . map screenColorMap . BS.unpack . BS.concatMap explodeWord

-- Create an image out of a packed `ByteString`.
-- See `bytesToPixels`
bytesToImage :: ByteString -> Int -> Int -> DynamicImage
bytesToImage bytes width height =
    let pixels = bytesToPixels bytes
    in ImageY8 (Image width height pixels)

-- | Create a text-based representation of a picture.
-- Background pixels are rendered as spaces, while
-- foreground are rendered as full block glyphs (█).
showAsciiArt :: TIBitmap -> Text
showAsciiArt (TIBitmap bytes) =
    let bits = BS.concatMap explodeWord bytes
        --hexChars = pack $ BS.foldr' showHex "" bits
        --imgChars = chunk _bmCols hexChars []
        glyphs = tiDecode $ BS.map toGlyph bits
        imgChars = chunk _bmCols glyphs []
    in intercalate "\n" imgChars
  where
    toGlyph :: Word8 -> Word8
    toGlyph 0x0 = 0x20
    toGlyph _ = 0xdf

    chunk :: Int -> Text -> [Text] -> [Text]
    chunk n "" chunks = chunks
    chunk n xs chunks = take n xs : chunk n (drop n xs) chunks

-- | Save a bitmap as a PNG image.
-- Bits are mapped to pixels using a vaguely calculator-like
-- color map (light gray background, dark gray foreground).
writePicPng :: FilePath -> TIBitmap -> IO ()
writePicPng path (TIBitmap bitmap) =
    let img = bytesToImage bitmap _bmCols _bmRows
    in savePngImage path img