@@ -2,294 +2,140 @@
22{-# LANGUAGE BangPatterns #-}
33{-# LANGUAGE NamedFieldPuns #-}
44{-# LANGUAGE DisambiguateRecordFields #-}
5−{-# OPTIONS_GHC -funbox-strict-fields #-}
6−module Graphics.Vty.Image ( DisplayString
7− , Image(..)
5+module Graphics.Vty.Image ( DisplayText
6+ , Image
87 , image_width
98 , image_height
9+ , horiz_join
1010 , (<|>)
11+ , vert_join
1112 , (<->)
1213 , horiz_cat
1314 , vert_cat
1415 , background_fill
16+ , text
17+ , strict_text
1518 , char
1619 , string
1720 , iso_10646_string
1821 , utf8_string
1922 , utf8_bytestring
23+ , utf8_strict_bytestring
2024 , char_fill
2125 , empty_image
22− , translate
2326 , safe_wcwidth
2427 , safe_wcswidth
2528 , wcwidth
2629 , wcswidth
2730 , crop
31+ , crop_right
32+ , crop_left
33+ , crop_bottom
34+ , crop_top
2835 , pad
36+ , resize
37+ , translate
2938 -- | The possible display attributes used in constructing an `Image`.
3039 , module Graphics.Vty.Attributes
3140 )
3241 where
3342
3443import Graphics.Vty.Attributes
35−
36−import Codec.Binary.UTF8.Width
37−
38−import Codec.Binary.UTF8.String ( decode )
39−
40−import Control.DeepSeq
41−
42−import qualified Data.ByteString as BS
43−import Data.Monoid
44−import qualified Data.Sequence as Seq
45−import qualified Data.String.UTF8 as UTF8
44+import Graphics.Vty.Image.Internal
45+import Graphics.Text.Width
46+
47+import qualified Data.ByteString as B
48+import qualified Data.ByteString.Lazy as BL
49+import qualified Data.Text as T
50+import qualified Data.Text.Encoding as T
51+import qualified Data.Text.Lazy as TL
52+import qualified Data.Text.Lazy.Encoding as TL
4653import Data.Word
4754
4855infixr 5 <|>
4956infixr 4 <->
5057
51−-- | We pair each character with it's display length. This way we only compute the length once per
52−-- character.
53−-- * Though currently the width of some strings is still compute multiple times.
54−type DisplayString = Seq.Seq (Char, Word)
55−
56−-- | An image in VTY defines:
57−--
58−-- * properties required to display the image. These are properties that effect the output image
59−-- but are independent of position
60−--
61−-- * A set of position-dependent text and attribute regions. The possible regions are:
58+-- | combines two images side by side
6259--
63−-- * a point. ( char )
60+-- Combines text chunks where possible. Assures output_width and output_height properties are not
61+-- violated.
6462--
65−-- * a horizontal line of characters with a single attribute. (string, utf8_string,
66−-- utf8_bytestring )
63+-- The result image will have a width equal to the sum of the two images width. And the height will
64+-- equal the largest height of the two images. The area not defined in one image due to a height
65+-- missmatch will be filled with the background pattern.
6766--
68−-- * a fill of a single character. (char_fill)
67+-- TODO: the bg fill is biased towards top to bottom languages(?)
68+horiz_join :: Image -> Image -> Image
69+horiz_join EmptyImage i = i
70+horiz_join i EmptyImage = i
71+horiz_join i_0@(HorizText a_0 t_0 w_0 cw_0) i_1@(HorizText a_1 t_1 w_1 cw_1)
72+ | a_0 == a_1 = HorizText a_0 (TL.append t_0 t_1) (w_0 + w_1) (cw_0 + cw_1)
73+ | otherwise = HorizJoin i_0 i_1 (w_0 + w_1) (image_height i_0 + image_height i_1)
74+horiz_join i_0 i_1
75+ -- If the images are of the same height then no padding is required
76+ | h_0 == h_1 = HorizJoin i_0 i_1 w h_0
77+ -- otherwise one of the images needs to be padded to the right size.
78+ | h_0 < h_1 -- Pad i_0
79+ = let pad_amount = h_1 - h_0
80+ in HorizJoin (VertJoin i_0 (BGFill w_0 pad_amount) w_0 h_1) i_1 w h_1
81+ | h_0 > h_1 -- Pad i_1
82+ = let pad_amount = h_0 - h_1
83+ in HorizJoin i_0 (VertJoin i_1 (BGFill w_1 pad_amount) w_1 h_0) w h_0
84+ where
85+ w_0 = image_width i_0
86+ w_1 = image_width i_1
87+ w = w_0 + w_1
88+ h_0 = image_height i_0
89+ h_1 = image_height i_1
90+horiz_join _ _ = error "horiz_join applied to undefined values."
91+
92+-- | combines two images vertically
6993--
70−-- * a fill of the picture's background. (background_fill)
94+-- The result image will have a height equal to the sum of the heights of both images.
95+-- The width will equal the largest width of the two images.
96+-- The area not defined in one image due to a width missmatch will be filled with the background
97+-- pattern.
7198--
72−-- todo: increase the number of encoded bytestring formats supported.
73−data Image =
74− -- | A horizontal text span is always >= 1 column and has a row height of 1.
75− HorizText
76− { attr :: !Attr
77− -- All character data is stored as Char sequences with the ISO-10646 encoding.
78− , text :: DisplayString
79− , output_width :: !Word -- >= 0
80− , char_width :: !Word -- >= 1
81− }
82− -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
83− -- required to be between two images of equal height. The horiz_join constructor adds background
84− -- filles to the provided images that assure this is true for the HorizJoin value produced.
85− | HorizJoin
86− { part_left :: Image
87− , part_right :: Image
88− , output_width :: !Word -- >= 1
89− , output_height :: !Word -- >= 1
90− }
91− -- | A veritical join can be constructed between any two images. However a VertJoin instance is
92− -- required to be between two images of equal width. The vert_join constructor adds background
93− -- fills to the provides images that assure this is true for the VertJoin value produced.
94− | VertJoin
95− { part_top :: Image
96− , part_bottom :: Image
97− , output_width :: !Word -- >= 1
98− , output_height :: !Word -- >= 1
99− }
100− -- | A background fill will be filled with the background pattern. The background pattern is
101− -- defined as a property of the Picture this Image is used to form.
102− | BGFill
103− { output_width :: !Word -- >= 1
104− , output_height :: !Word -- >= 1
105− }
106− -- | The combining operators identity constant.
107− -- EmptyImage <|> a = a
108− -- EmptyImage <-> a = a
109− --
110− -- Any image of zero size equals the empty image.
111− | EmptyImage
112− | Translation (Int, Int) Image
113− -- Crop an image to a size
114− | ImageCrop (Word, Word) Image
115− -- Pad an image up to a size
116− | ImagePad (Word, Word) Image
117− deriving Eq
118−
119−instance Show Image where
120− show ( HorizText { output_width = ow, text = txt } )
121− = "HorizText [" ++ show ow ++ "] (" ++ show (fmap fst txt) ++ ")"
122− show ( BGFill { output_width = c, output_height = r } )
123− = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
124− show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
125− = "HorizJoin " ++ show c ++ " ( " ++ show l ++ " <|> " ++ show r ++ " )"
126− show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
127− = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
128− show ( Translation offset i )
129− = "Translation " ++ show offset ++ " ( " ++ show i ++ " )"
130− show ( ImageCrop size i )
131− = "ImageCrop " ++ show size ++ " ( " ++ show i ++ " )"
132− show ( ImagePad size i )
133− = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"
134− show ( EmptyImage ) = "EmptyImage"
135−
136−-- | Currently append in the Monoid instance is equivalent to <->.
137−instance Monoid Image where
138− mempty = empty_image
139− mappend = (<->)
140−
141−instance NFData Image where
142− rnf EmptyImage = ()
143− rnf (Translation s i) = s `deepseq` i `deepseq` ()
144− rnf (ImagePad s i) = s `deepseq` i `deepseq` ()
145− rnf (ImageCrop s i) = s `deepseq` i `deepseq` ()
146− rnf (BGFill !w !h) = ()
147− rnf (VertJoin t b !w !h) = t `deepseq` b `deepseq` ()
148− rnf (HorizJoin l r !w !h) = l `deepseq` r `deepseq` ()
149− rnf (HorizText !a s !w !cw) = s `deepseq` ()
150−
151−-- A horizontal text image of 0 characters in width simplifies to the EmptyImage
152−horiz_text :: Attr -> DisplayString -> Word -> Image
153−horiz_text a txt ow
154− | ow == 0 = EmptyImage
155− | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)
156−
157−horiz_join :: Image -> Image -> Word -> Word -> Image
158−horiz_join i_0 i_1 w h
159− -- A horiz join of two 0 width images simplifies to the EmptyImage
160− | w == 0 = EmptyImage
161− -- A horizontal join where either part is 0 columns in width simplifies to the other part.
162− -- This covers the case where one part is the EmptyImage.
163− | image_width i_0 == 0 = i_1
164− | image_width i_1 == 0 = i_0
165− -- If the images are of the same height then no BG padding is required
166− | image_height i_0 == image_height i_1 = HorizJoin i_0 i_1 w h
167− -- otherwise one of the imagess needs to be padded to the right size.
168− | image_height i_0 < image_height i_1 -- Pad i_0
169− = let pad_amount = image_height i_1 - image_height i_0
170− in horiz_join ( vert_join i_0
171− ( BGFill ( image_width i_0 ) pad_amount )
172− ( image_width i_0 )
173− ( image_height i_1 )
174− )
175− i_1
176− w h
177− | image_height i_0 > image_height i_1 -- Pad i_1
178− = let pad_amount = image_height i_0 - image_height i_1
179− in horiz_join i_0
180− ( vert_join i_1
181− ( BGFill ( image_width i_1 ) pad_amount )
182− ( image_width i_1 )
183− ( image_height i_0 )
184− )
185− w h
186−horiz_join _ _ _ _ = error "horiz_join applied to undefined values."
187−
188−vert_join :: Image -> Image -> Word -> Word -> Image
189−vert_join i_0 i_1 w h
190− -- A vertical join of two 0 height images simplifies to the EmptyImage
191− | h == 0 = EmptyImage
192− -- A vertical join where either part is 0 rows in height simplifies to the other part.
193− -- This covers the case where one part is the EmptyImage
194− | image_height i_0 == 0 = i_1
195− | image_height i_1 == 0 = i_0
99+-- TODO: the bg fill is biased towards right to left languages(?)
100+vert_join :: Image -> Image -> Image
101+vert_join EmptyImage i = i
102+vert_join i EmptyImage = i
103+vert_join i_0 i_1
196104 -- If the images are of the same height then no background padding is required
197− | image_width i_0 == image_width i_1 = VertJoin i_0 i_1 w h
105+ | w_0 == w_1 = VertJoin i_0 i_1 w_0 h
198106 -- Otherwise one of the images needs to be padded to the size of the other image.
199− | image_width i_0 < image_width i_1
200− = let pad_amount = image_width i_1 - image_width i_0
201− in vert_join ( horiz_join i_0
202− ( BGFill pad_amount ( image_height i_0 ) )
203− ( image_width i_1 )
204− ( image_height i_0 )
205− )
206− i_1
207− w h
208− | image_width i_0 > image_width i_1
209− = let pad_amount = image_width i_0 - image_width i_1
210− in vert_join i_0
211− ( horiz_join i_1
212− ( BGFill pad_amount ( image_height i_1 ) )
213− ( image_width i_0 )
214− ( image_height i_1 )
215− )
216− w h
217−vert_join _ _ _ _ = error "vert_join applied to undefined values."
107+ | w_0 < w_1
108+ = let pad_amount = w_1 - w_0
109+ in VertJoin (HorizJoin i_0 (BGFill pad_amount h_0) w_1 h_0) i_1 w_1 h
110+ | w_0 > w_1
111+ = let pad_amount = w_0 - w_1
112+ in VertJoin i_0 (HorizJoin i_1 (BGFill pad_amount h_1) w_0 h_1) w_0 h
113+ where
114+ w_0 = image_width i_0
115+ w_1 = image_width i_1
116+ h_0 = image_height i_0
117+ h_1 = image_height i_1
118+ h = h_0 + h_1
119+vert_join _ _ = error "vert_join applied to undefined values."
218120
219121-- | An area of the picture's bacground (See Background) of w columns and h rows.
220−background_fill :: Word -> Word -> Image
122+background_fill :: Int -> Int -> Image
221123background_fill w h
222124 | w == 0 = EmptyImage
223125 | h == 0 = EmptyImage
224126 | otherwise = BGFill w h
225127
226−-- | The width of an Image. This is the number display columns the image will occupy.
227−image_width :: Image -> Word
228−image_width HorizText { output_width = w } = w
229−image_width HorizJoin { output_width = w } = w
230−image_width VertJoin { output_width = w } = w
231−image_width BGFill { output_width = w } = w
232−image_width EmptyImage = 0
233−image_width ( Translation v i ) = toEnum $ max 0 $ (fst v +) $ fromEnum $ image_width i
234−image_width ( ImageCrop v i ) = min (image_width i) $ fst v
235−image_width ( ImagePad v i ) = max (image_width i) $ fst v
236−
237−-- | The height of an Image. This is the number of display rows the image will occupy.
238−image_height :: Image -> Word
239−image_height HorizText {} = 1
240−image_height HorizJoin { output_height = r } = r
241−image_height VertJoin { output_height = r } = r
242−image_height BGFill { output_height = r } = r
243−image_height EmptyImage = 0
244−image_height ( Translation v i ) = toEnum $ max 0 $ (snd v +) $ fromEnum $ image_height i
245−image_height ( ImageCrop v i ) = min (image_height i) $ snd v
246−image_height ( ImagePad v i ) = max (image_height i) $ snd v
247−
248−-- | Combines two images side by side.
128+-- | Combines two images horizontally. Alias for horiz_join
249129--
250−-- The result image will have a width equal to the sum of the two images width. And the height will
251−-- equal the largest height of the two images. The area not defined in one image due to a height
252−-- missmatch will be filled with the background pattern.
130+-- infixr 5
253131(<|>) :: Image -> Image -> Image
132+(<|>) = horiz_join
254133
255−-- Two horizontal text spans with the same attributes can be merged.
256−h0@(HorizText attr_0 text_0 ow_0 _) <|> h1@(HorizText attr_1 text_1 ow_1 _)
257− | attr_0 == attr_1 = horiz_text attr_0 (text_0 Seq.>< text_1) (ow_0 + ow_1)
258− | otherwise = horiz_join h0 h1 (ow_0 + ow_1) 1
259−
260−-- Anything placed to the right of a join wil be joined to the right sub image.
261−-- The total columns for the join is the sum of the two arguments columns
262−h0@( HorizJoin {} ) <|> h1
263− = horiz_join ( part_left h0 )
264− ( part_right h0 <|> h1 )
265− ( image_width h0 + image_width h1 )
266− ( max (image_height h0) (image_height h1) )
267−
268−-- Anything but a join placed to the left of a join wil be joined to the left sub image.
269−-- The total columns for the join is the sum of the two arguments columns
270−h0 <|> h1@( HorizJoin {} )
271− = horiz_join ( h0 <|> part_left h1 )
272− ( part_right h1 )
273− ( image_width h0 + image_width h1 )
274− ( max (image_height h0) (image_height h1) )
275−
276−h0 <|> h1
277− = horiz_join h0
278− h1
279− ( image_width h0 + image_width h1 )
280− ( max (image_height h0) (image_height h1) )
281−
282−-- | Combines two images vertically.
283−-- The result image will have a height equal to the sum of the heights of both images.
284−-- The width will equal the largest width of the two images.
285−-- The area not defined in one image due to a width missmatch will be filled with the background
286−-- pattern.
134+-- | Combines two images vertically. Alias for vert_join
135+--
136+-- infixr 4
287137(<->) :: Image -> Image -> Image
288−im_t <-> im_b
289− = vert_join im_t
290− im_b
291− ( max (image_width im_t) (image_width im_b) )
292− ( image_height im_t + image_height im_b )
138+(<->) = vert_join
293139
294140-- | Compose any number of images horizontally.
295141horiz_cat :: [Image] -> Image
@@ -299,12 +145,26 @@ horiz_cat = foldr (<|>) EmptyImage
299145vert_cat :: [Image] -> Image
300146vert_cat = foldr (<->) EmptyImage
301147
148+-- | A Data.Text.Lazy value
149+text :: Attr -> TL.Text -> Image
150+text a txt
151+ | TL.length txt == 0 = EmptyImage
152+ | otherwise = let display_width = safe_wcswidth (TL.unpack txt)
153+ in HorizText a txt display_width (fromIntegral $! TL.length txt)
154+
155+-- | A Data.Text value
156+strict_text :: Attr -> T.Text -> Image
157+strict_text a txt
158+ | T.length txt == 0 = EmptyImage
159+ | otherwise = let display_width = safe_wcswidth (T.unpack txt)
160+ in HorizText a (TL.fromStrict txt) display_width (T.length txt)
161+
302162-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
303163-- the ISO-10646 encoding.
304164char :: Attr -> Char -> Image
305−char !a !c =
165+char a c =
306166 let display_width = safe_wcwidth c
307− in HorizText a (Seq.singleton (c, display_width)) display_width 1
167+ in HorizText a (TL.singleton c) display_width 1
308168
309169-- | A string of characters layed out on a single row with the same display attribute. The string is
310170-- assumed to be a sequence of ISO-10646 characters.
@@ -316,9 +176,9 @@ char !a !c =
316176-- directly to iso_10646_string or string.
317177--
318178iso_10646_string :: Attr -> String -> Image
319−iso_10646_string !a !str =
320− let display_text = Seq.fromList $ map (\c -> (c, safe_wcwidth c)) str
321− in horiz_text a display_text (safe_wcswidth str)
179+iso_10646_string a str =
180+ let display_width = safe_wcswidth str
181+ in HorizText a (TL.pack str) display_width (length str)
322182
323183-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
324184-- represented internally as a list of ISO 10646 31 bit characters.
@@ -329,62 +189,110 @@ iso_10646_string !a !str =
329189string :: Attr -> String -> Image
330190string = iso_10646_string
331191
332−-- | A string of characters layed out on a single row. The string is assumed to be a sequence of
333−-- UTF-8 characters.
192+-- | A string of characters layed out on a single row. The input is assumed to be the bytes for
193+-- UTF-8 encoded text.
334194utf8_string :: Attr -> [Word8] -> Image
335−utf8_string !a !str = string a ( decode str )
195+utf8_string a bytes = utf8_bytestring a (BL.pack bytes)
336196
337−-- XXX: Characters with unknown widths occupy 1 column?
338−--
339−-- Not sure if this is actually correct. I presume there is a replacement character that is output
340−-- by the terminal instead of the character and this replacement character is 1 column wide. If this
341−-- is not true for all terminals then a per-terminal replacement character width needs to be
342−-- implemented.
197+-- | Renders a UTF-8 encoded lazy bytestring.
198+utf8_bytestring :: Attr -> BL.ByteString -> Image
199+utf8_bytestring a bs = text a (TL.decodeUtf8 bs)
343200
344−-- | Returns the display width of a character. Assumes all characters with unknown widths are 1 width
345−safe_wcwidth :: Char -> Word
346−safe_wcwidth c = case wcwidth c of
347− i | i < 0 -> 1
348− | otherwise -> toEnum i
349−
350−-- | Returns the display width of a string. Assumes all characters with unknown widths are 1 width
351−safe_wcswidth :: String -> Word
352−safe_wcswidth str = case wcswidth str of
353− i | i < 0 -> 1
354− | otherwise -> toEnum i
355−
356−-- | Renders a UTF-8 encoded bytestring.
357−utf8_bytestring :: Attr -> BS.ByteString -> Image
358−utf8_bytestring !a !bs = string a (UTF8.toString $ UTF8.fromRep bs)
201+-- | Renders a UTF-8 encoded strict bytestring.
202+utf8_strict_bytestring :: Attr -> B.ByteString -> Image
203+utf8_strict_bytestring a bs = strict_text a (T.decodeUtf8 bs)
359204
360205-- | creates a fill of the specified character. The dimensions are in number of characters wide and
361206-- number of rows high.
362207--
363208-- Unlike the Background fill character this character can have double column display width.
364−char_fill :: Enum d => Attr -> Char -> d -> d -> Image
365−char_fill !a !c w h =
366− vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) $ char a c
209+char_fill :: Integral d => Attr -> Char -> d -> d -> Image
210+char_fill _a _c 0 _h = EmptyImage
211+char_fill _a _c _w 0 = EmptyImage
212+char_fill a c w h =
213+ vert_cat $ replicate (fromIntegral h) $ HorizText a txt display_width char_width
214+ where
215+ txt = TL.replicate (fromIntegral w) (TL.singleton c)
216+ display_width = safe_wcwidth c * (fromIntegral w)
217+ char_width = fromIntegral w
367218
368219-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
369220-- attributes.
370221empty_image :: Image
371222empty_image = EmptyImage
372223
373−-- | Apply the given offset to the image.
374−translate :: (Int, Int) -> Image -> Image
375−translate v i = Translation v i
376−
377−-- | Ensure an image is no larger than the provided size. If the image is larger then crop.
378−crop :: (Word, Word) -> Image -> Image
379−crop (0,_) _ = EmptyImage
380−crop (_,0) _ = EmptyImage
381−crop v (ImageCrop _size i) = ImageCrop (min (fst v) (fst _size), min (snd v) (snd _size)) i
382−crop v i = ImageCrop v i
383−
384−-- | Ensure an image is at least the provided size. If the image is smaller then pad.
385−pad :: (Word, Word) -> Image -> Image
386−pad (0,_) _ = EmptyImage
387−pad (_,0) _ = EmptyImage
388−pad v (ImagePad _size i) = ImagePad (max (fst v) (fst _size), max (snd v) (snd _size)) i
389−pad v i = ImagePad v i
224+-- | pad the given image. This adds background character fills to the left, top, right, bottom.
225+pad :: Int -> Int -> Int -> Int -> Image -> Image
226+pad 0 0 0 0 i = i
227+pad in_l in_t in_r in_b in_image
228+ | in_l < 0 || in_t < 0 || in_r < 0 || in_b < 0 = error "cannot pad by negative amount"
229+ | otherwise = go in_l in_t in_r in_b in_image
230+ where
231+ -- TODO: uh.
232+ go 0 0 0 0 i = i
233+ go 0 0 0 b i = VertJoin i (BGFill w b) w h
234+ where w = image_width i
235+ h = image_height i + b
236+ go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
237+ where w = image_width i + r
238+ h = image_height i
239+ go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
240+ where w = image_width i
241+ h = image_height i + t
242+ go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
243+ where w = image_width i + l
244+ h = image_height i
245+
246+-- | "translates" an image by padding the top and left.
247+translate :: Int -> Int -> Image -> Image
248+translate x y i = pad x y 0 0 i
249+
250+-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right
251+-- or bottom.
252+--
253+-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the
254+-- right.
255+crop :: Int -> Int -> Image -> Image
256+crop 0 _ _ = EmptyImage
257+crop _ 0 _ = EmptyImage
258+crop w h i = crop_bottom h (crop_right w i)
259+
260+-- | crop the display height. If the image is less than or equal in height then this operation has
261+-- no effect. Otherwise the image is cropped from the bottom.
262+crop_bottom :: Int -> Image -> Image
263+crop_bottom 0 _ = EmptyImage
264+crop_bottom h in_i
265+ | h < 0 = error "cannot crop height to less than zero"
266+ | otherwise = go in_i
267+ where
268+ go EmptyImage = EmptyImage
269+ go (CropBottom {cropped_image, output_width, output_height}) =
270+ CropBottom cropped_image output_width (min h output_height)
271+ go i
272+ | h >= image_height i = i
273+ | otherwise = CropBottom i (image_width i) h
274+
275+crop_right :: Int -> Image -> Image
276+crop_right 0 _ = EmptyImage
277+crop_right w in_i
278+ | w < 0 = error "cannot crop width to less than zero"
279+ | otherwise = go in_i
280+ where
281+ go EmptyImage = EmptyImage
282+ go (CropRight {cropped_image, output_width, output_height}) =
283+ CropRight cropped_image (min w output_width) output_height
284+ go i
285+ | w >= image_width i = i
286+ | otherwise = CropRight i w (image_height i)
287+
288+crop_left :: Int -> Image -> Image
289+crop_left _ _ = error "not implemented"
290+
291+crop_top :: Int -> Image -> Image
292+crop_top _ _ = error "not implemented"
293+
294+-- | Generic resize. Pads and crops as required to assure the given display width and height.
295+-- This is biased to pad the right and bottom.
296+resize :: Int -> Int -> Image -> Image
297+resize _w _h _i = error "not implemented yet"
390298