packages feed

png-chunk-yaftee-0.1.0.1: src/Control/Monad/Yaftee/Pipe/Png/Chunk.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE BlockArguments, LambdaCase, OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds, ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module Control.Monad.Yaftee.Pipe.Png.Chunk (

	-- * DATA TYPE

	C(..),

	-- * DECODE

	decodeRun_, DecodeStates, decode, hDecode, DecodeMembers,

	-- * ENCODE

	encodeRun_, EncodeStates, encode, hEncode, EncodeMembers

	) where

import Control.Monad
import Control.Monad.Fix
import Control.Monad.Yaftee.Eff qualified as Eff
import Control.Monad.Yaftee.Pipe qualified as Pipe
import Control.Monad.Yaftee.Pipe.Tools qualified as PipeT
import Control.Monad.Yaftee.Pipe.ByteString qualified as PipeBS
import Control.Monad.Yaftee.Pipe.ByteString.FingerTree.OnDemand
	qualified as OnDemand
import Control.Monad.Yaftee.Pipe.MonoTraversable.Crc32
	qualified as PipeCrc32
import Control.Monad.Yaftee.State qualified as State
import Control.Monad.Yaftee.Except qualified as Except
import Control.Monad.Yaftee.Fail qualified as Fail
import Control.Monad.Yaftee.IO qualified as IO
import Control.HigherOpenUnion qualified as U
import Data.Foldable
import Data.MonoTraversable
import Data.Word
import Data.Word.Word8 qualified as Word8
import Data.Word.Crc32 qualified as Crc32
import Data.ByteString.FingerTree qualified as BSF
import Data.ByteString.FingerTree.Bits qualified as BSF
import System.IO	

import Data.HigherFunctor qualified as F
import Data.TypeLevel.List

import Numeric

decodeRun_ ::
	F.Loose (U.U es) =>
	Eff.E (DecodeStates nm `Append` es) i o r -> Eff.E es i o ()
decodeRun_ = void . (`State.runN` Crc32.initial)

type DecodeStates nm = '[State.Named nm Crc32.C]


decode :: forall nm -> (
	U.Member Pipe.P es, DecodeMembers nm es,
	U.Member (Except.E String) es ) =>
	Int -> Eff.E es BSF.ByteString C ()
decode nm n = void $ OnDemand.onDemand nm Pipe.=$= PipeCrc32.crc32 nm Pipe.=$= do
	State.putN nm $ OnDemand.RequestBytes 8
	fh <- Pipe.await
	when (fh /= fileHeader) . Except.throw @String
		$ "Not PNG file: " ++ show fh
	chunks nm n

hDecode :: forall nm -> (
	U.Member Pipe.P es, DecodeMembers nm es,
	U.Member (Except.E String) es, U.Base IO.I es ) =>
	Handle -> Int -> Int -> Eff.E es BSF.ByteString C ()
hDecode nm h n n' = void $
	PipeBS.hGet n h Pipe.=$=
	PipeT.convert BSF.fromStrict Pipe.=$=
	decode nm n'

type DecodeMembers nm es = (
	OnDemand.Members nm es,
	U.Member (State.Named nm Crc32.C) es )

chunks :: forall nm -> (
	U.Member Pipe.P es, OnDemand.Members nm es,
	U.Member (State.Named nm Crc32.C) es, U.Member (Except.E String) es ) =>
	Int -> Eff.E es BSF.ByteString C ()
chunks nm d = fix \go -> do
	b <- chunk1 nm d
	if b then go else Pipe.yield EndOfTheWorld

chunk1 :: forall nm -> (
	U.Member Pipe.P es, OnDemand.Members nm es,
	U.Member (State.Named nm Crc32.C) es, U.Member (Except.E String) es ) =>
	Int -> Eff.E es BSF.ByteString C Bool
chunk1 nm d = do
	State.putN nm $ OnDemand.RequestBytes 4
	n <- Word8.toBitsBE @_ @Int <$> Pipe.await
	PipeCrc32.reset nm
	State.putN nm $ OnDemand.RequestBytes 4
	cn <- Pipe.await
--	trace ("chunk1: yield Begin: " ++ show cn) (Pipe.yield $ Begin n cn)
	Pipe.yield $ Begin n cn
	for_ (split d n) \n' -> do
		State.putN nm $ OnDemand.RequestBytes n'
		Pipe.yield . Body =<< Pipe.await
	PipeCrc32.complement nm
	c1 <- State.getN @Crc32.C nm
	State.putN nm $ OnDemand.RequestBytes 4
	c0 <- Crc32.fromWord . Word8.toBitsBE @_ @Word32 <$> Pipe.await
	when (c1 /= c0) $ Except.throw @String
		$ "corrupted -- crc32 mismatch: " ++
			showHex (Crc32.toWord c0) "" ++ " " ++ showHex (Crc32.toWord c1) ""
--	trace ("chunk1: yield End: " ++ show cn) $ Pipe.yield End
	Pipe.yield End
	pure $ cn /= "IEND"
	where
	split n = fix \go -> \case
		0 -> []
		m | n < m -> n : go (m - n) | otherwise -> [m]

data C	= Begin Int BSF.ByteString
	| Body BSF.ByteString | End
	| EndOfTheWorld
	deriving Show

fileHeader :: BSF.ByteString
fileHeader = "\137PNG\r\n\SUB\n"

type instance Element C = Word8

instance MonoFoldable C where
	ofoldMap f = \case
		Begin _ bs -> ofoldMap f bs
		Body bs -> ofoldMap f bs; _ -> mempty
	ofoldr o v = \case
		Begin _ bs -> ofoldr o v bs
		Body bs -> ofoldr o v bs; _ -> v
	ofoldl' o v = \case
		Begin _ bs -> ofoldl' o v bs
		Body bs -> ofoldl' o v bs; _ -> v
	ofoldr1Ex o = \case
		Begin _ bs -> ofoldr1Ex o bs
		Body bs -> ofoldr1Ex o bs; _ -> error "instance MonoFoldable C: bad"
	ofoldl1Ex' o = \case
		Begin _ bs -> ofoldl1Ex' o bs
		Body bs -> ofoldl1Ex' o bs; _ -> error "instance MonoFoldable C: bad"

encode :: forall nm -> (
	U.Member Pipe.P es, U.Member (State.Named nm Crc32.C) es,
	U.Member (Except.E String) es, U.Member Fail.F es ) =>
	Eff.E es C BSF.ByteString ()
encode nm = void $
	PipeCrc32.crc32 nm
	Pipe.=$= do
		Pipe.yield fileHeader
		fix \go -> (`when` go) =<< encodeChunk1 nm

hEncode :: forall nm -> (
	U.Member Pipe.P es, U.Member (State.Named nm Crc32.C) es,
	U.Member (Except.E String) es, U.Member Fail.F es, U.Base IO.I es ) =>
	Handle -> Eff.E es C o ()
hEncode nm ho = void $ encode nm
	Pipe.=$= PipeT.convert BSF.toStrict
	Pipe.=$= PipeBS.hPutStr ho

encodeChunk1 :: forall nm -> (
	U.Member Pipe.P es, U.Member (State.Named nm Crc32.C) es,
	U.Member (Except.E String) es, U.Member Fail.F es ) =>
	Eff.E es C BSF.ByteString Bool
encodeChunk1 nm = do
	PipeCrc32.reset nm
	Begin n cn <- Pipe.await
	Pipe.yield . BSF.fromBitsBE' @Word32 $ fromIntegral n
	Pipe.yield cn
	fix \go -> Pipe.await >>= \case
		Body bd -> Pipe.yield bd >> go
		End -> pure ()
		_ -> Except.throw @String "encodeChunk1: bad"
	PipeCrc32.complement nm
	c <- State.getN @Crc32.C nm
	Pipe.yield . BSF.fromBitsBE' $ Crc32.toWord c
	pure $ cn /= "IEND"

type EncodeMembers nm es = (
	U.Member (State.Named nm Crc32.C) es
	)

encodeRun_ :: forall nm es i o r .
	F.Loose (U.U es) =>
	Eff.E (EncodeStates nm `Append` es) i o r ->
	Eff.E es i o ()
encodeRun_ = void . PipeCrc32.run @nm

type EncodeStates nm = '[State.Named nm Crc32.C]