on master help sign in
Merge branches 'dev/fix-translate' and 'dev/add-cropped-images' into dev/integration
a5235e4Ben Boeckel
src/Graphics/Vty/Image.hs+29−3
@@ -19,6 +19,8 @@ module Graphics.Vty.Image ( Image(..)
19 , char_fill
19 , char_fill
20 , empty_image
20 , empty_image
21 , translate
21 , translate
22+ , crop
23+ , pad
22 -- | The possible display attributes used in constructing an `Image`.
24 -- | The possible display attributes used in constructing an `Image`.
23 , module Graphics.Vty.Attributes
25 , module Graphics.Vty.Attributes
24 )
26 )
@@ -99,6 +101,10 @@ data Image =
99 -- Any image of zero size equals the empty image.
101 -- Any image of zero size equals the empty image.
100 | EmptyImage
102 | EmptyImage
101 | Translation (Int, Int) Image
103 | Translation (Int, Int) Image
104+ -- Crop an image to a size
105+ | ImageCrop (Word, Word) Image
106+ -- Pad an image up to a size
107+ | ImagePad (Word, Word) Image
102 deriving Eq
108 deriving Eq
103
109
104instance Show Image where
110instance Show Image where
@@ -111,7 +117,11 @@ instance Show Image where
111 show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
117 show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
112 = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
118 = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
113 show ( Translation offset i )
119 show ( Translation offset i )
114− = "Translation ( " ++ show offset ++ ", " ++ show i ++ " )"
120+ = "Translation " ++ show offset ++ " ( " ++ show i ++ " )"
121+ show ( ImageCrop size i )
122+ = "ImageCrop " ++ show size ++ " ( " ++ show i ++ " )"
123+ show ( ImagePad size i )
124+ = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"
115 show ( EmptyImage ) = "EmptyImage"
125 show ( EmptyImage ) = "EmptyImage"
116
126
117-- | Currently append in the Monoid instance is equivalent to <->. Future versions will just stack
127-- | Currently append in the Monoid instance is equivalent to <->. Future versions will just stack
@@ -202,7 +212,9 @@ image_width HorizJoin { output_width = w } = w
202image_width VertJoin { output_width = w } = w
212image_width VertJoin { output_width = w } = w
203image_width BGFill { output_width = w } = w
213image_width BGFill { output_width = w } = w
204image_width EmptyImage = 0
214image_width EmptyImage = 0
205−image_width ( Translation _v i ) = image_width i
215+image_width ( Translation _v i ) = toEnum $ max 0 $ (fst _v +) $ fromEnum $ image_width i
216+image_width ( ImageCrop _v i ) = min (image_width i) $ fst _v
217+image_width ( ImagePad _v i ) = max (image_width i) $ fst _v
206
218
207-- | The height of an Image. This is the number of display rows the image will occupy.
219-- | The height of an Image. This is the number of display rows the image will occupy.
208image_height :: Image -> Word
220image_height :: Image -> Word
@@ -211,7 +223,9 @@ image_height HorizJoin { output_height = r } = r
211image_height VertJoin { output_height = r } = r
223image_height VertJoin { output_height = r } = r
212image_height BGFill { output_height = r } = r
224image_height BGFill { output_height = r } = r
213image_height EmptyImage = 0
225image_height EmptyImage = 0
214−image_height ( Translation _v i ) = image_height i
226+image_height ( Translation _v i ) = toEnum $ max 0 $ (snd _v +) $ fromEnum $ image_height i
227+image_height ( ImageCrop _v i ) = min (image_height i) $ snd _v
228+image_height ( ImagePad _v i ) = max (image_height i) $ snd _v
215
229
216-- | Combines two images side by side.
230-- | Combines two images side by side.
217--
231--
@@ -328,3 +342,15 @@ empty_image = EmptyImage
328translate :: (Int, Int) -> Image -> Image
342translate :: (Int, Int) -> Image -> Image
329translate v i = Translation v i
343translate v i = Translation v i
330
344
345+crop :: (Word, Word) -> Image -> Image
346+crop (0,_) _ = EmptyImage
347+crop (_,0) _ = EmptyImage
348+crop v (ImageCrop _size i) = ImagePad (min (fst v) (fst _size), min (snd v) (snd _size)) i
349+crop v (ImagePad _size i) = ImagePad (min (fst v) (fst _size), min (snd v) (snd _size)) i
350+crop v i = ImagePad v i
351+
352+pad :: (Word, Word) -> Image -> Image
353+pad (0,_) _ = EmptyImage
354+pad (_,0) _ = EmptyImage
355+pad v (ImagePad _size i) = ImagePad (max (fst v) (fst _size), max (snd v) (snd _size)) i
356+pad v i = ImagePad v i
src/Graphics/Vty/Picture.hs+2
@@ -16,6 +16,8 @@ module Graphics.Vty.Picture ( module Graphics.Vty.Picture
16 , char_fill
16 , char_fill
17 , empty_image
17 , empty_image
18 , translate
18 , translate
19+ , crop
20+ , pad
19 -- | The possible display attributes used in constructing an `Image`.
21 -- | The possible display attributes used in constructing an `Image`.
20 , module Graphics.Vty.Attributes
22 , module Graphics.Vty.Attributes
21 )
23 )
src/Graphics/Vty/Span.hs+95−41
@@ -24,6 +24,7 @@ import Data.Array.ST
24import qualified Data.ByteString as B
24import qualified Data.ByteString as B
25import qualified Data.ByteString.Internal as BInt
25import qualified Data.ByteString.Internal as BInt
26import qualified Data.Foldable as Foldable
26import qualified Data.Foldable as Foldable
27+import Data.List
27import Data.Word
28import Data.Word
28import qualified Data.ByteString.UTF8 as BSUTF8
29import qualified Data.ByteString.UTF8 as BSUTF8
29import qualified Data.String.UTF8 as UTF8
30import qualified Data.String.UTF8 as UTF8
@@ -110,7 +111,7 @@ build_spans pic region = do
110 -- The ops builder recursively descends the image and outputs span ops that would
111 -- The ops builder recursively descends the image and outputs span ops that would
111 -- display that image. The number of columns remaining in this row before exceeding the
112 -- display that image. The number of columns remaining in this row before exceeding the
112 -- bounds is also provided. This is used to clip the span ops produced.
113 -- bounds is also provided. This is used to clip the span ops produced.
113− ops_for_row mrow_ops (pic_background pic) region (pic_image pic) 0 (region_width region)
114+ _ <- ops_for_row mrow_ops (pic_background pic) region (pic_image pic) (0,0) 0 (region_width region)
114 -- Fill in any unspecified columns with the background pattern.
115 -- Fill in any unspecified columns with the background pattern.
115 forM_ [0 .. region_height region - 1] $ \row -> do
116 forM_ [0 .. region_height region - 1] $ \row -> do
116 end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
117 end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
@@ -122,61 +123,105 @@ build_spans pic region = do
122
123
123type MRowOps s = STArray s Word SpanOps
124type MRowOps s = STArray s Word SpanOps
124
125
125−ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> Word -> Word -> ST s ()
126+ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> (Word, Word) -> Word -> Word -> ST s (Word, Word)
126−ops_for_row mrow_ops bg region image y remaining_columns
127+ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_columns
127− | remaining_columns == 0 = return ()
128+ | remaining_columns == 0 = return skip_dim
128− | y >= region_height region = return ()
129+ | y >= region_height region = return skip_dim
129 | otherwise = case image of
130 | otherwise = case image of
130− EmptyImage -> return ()
131+ EmptyImage -> return skip_dim
131 -- The width provided is the number of columns this text span will occupy when displayed.
132 -- The width provided is the number of columns this text span will occupy when displayed.
132 -- if this is greater than the number of remaining columsn the output has to be produced a
133 -- if this is greater than the number of remaining columsn the output has to be produced a
133 -- character at a time.
134 -- character at a time.
134− HorizText a text_str ow cw -> do
135+ HorizText a text_str _ _ -> do
136+ if skip_row > 0
137+ then return (skip_row - 1, skip_col)
138+ else do
135− snoc_text_span a text_str ow cw mrow_ops y remaining_columns
139+ skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns
140+ return (skip_row, skip_col')
136 VertJoin t b _ _ -> do
141 VertJoin t b _ _ -> do
137− ops_for_row mrow_ops bg region t y remaining_columns
142+ (skip_row',skip_col') <- ops_for_row mrow_ops bg region t skip_dim y remaining_columns
138− ops_for_row mrow_ops bg region b (y + image_height t) remaining_columns
143+ (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region b (skip_row', skip_col) (y + image_height t - (skip_row - skip_row')) remaining_columns
144+ return (skip_row'', min skip_col' skip_col'')
139 HorizJoin l r _ _ -> do
145 HorizJoin l r _ _ -> do
140− ops_for_row mrow_ops bg region l y remaining_columns
146+ (skip_row',skip_col') <- ops_for_row mrow_ops bg region l skip_dim y remaining_columns
141 -- Don't output the right part unless there is at least a single column left after
147 -- Don't output the right part unless there is at least a single column left after
142 -- outputting the left part.
148 -- outputting the left part.
143− if image_width l < remaining_columns
149+ if image_width l - (skip_col - skip_col') > remaining_columns
150+ then return (skip_row,skip_col')
151+ else do
144− then ops_for_row mrow_ops bg region r y (remaining_columns - image_width l)
152+ (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region r (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col'))
145− else return ()
153+ return (min skip_row' skip_row'', skip_col'')
146 BGFill width height -> do
154 BGFill width height -> do
147− let actual_height = if y + height > region_height region
155+ let min_height = if y + height > region_height region
148− then region_height region - y
156+ then region_height region - y
149− else height
157+ else height
158+ min_width = if width > remaining_columns
159+ then remaining_columns
160+ else width
161+ actual_height = if skip_row > min_height
162+ then 0
163+ else min_height - skip_row
150− actual_width = if width > remaining_columns
164+ actual_width = if skip_col > min_width
151− then remaining_columns
165+ then 0
152− else width
166+ else min_width - skip_col
153 forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
167 forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
154− Translation _offset i -> ops_for_row mrow_ops bg region i y remaining_columns
168+ let skip_row' = if actual_height > skip_row
169+ then 0
170+ else skip_row - min_height
171+ skip_col' = if actual_width > skip_col
172+ then 0
173+ else skip_col - min_width
174+ return (skip_row',skip_col')
175+ Translation (dx,dy) i -> do
176+ if dx < 0
177+ -- Translation left
178+ -- Extract the delta and add it to skip_col.
179+ then ops_for_row mrow_ops bg region (translate (0, dy) i) (skip_row, skip_col + dw) y remaining_columns
180+ -- Translation right
181+ else if dy < 0
182+ -- Translation up
183+ -- Extract the delta and add it to skip_row.
184+ then ops_for_row mrow_ops bg region (translate (dx, 0) i) (skip_row + dh, skip_col) y remaining_columns
185+ -- Translation down
186+ -- Pad the start of lines and above the image with a
187+ -- background_fill image.
188+ else ops_for_row mrow_ops bg region (background_fill ow dh <-> (background_fill dw ih <|> i)) skip_dim y remaining_columns
189+ where
190+ dw = toEnum $ abs dx
191+ dh = toEnum $ abs dy
192+ ow = image_width image
193+ ih = image_height i
194+ ImageCrop (max_w,max_h) i ->
195+ if y >= max_h
196+ then return skip_dim
197+ else ops_for_row mrow_ops bg region i skip_dim y (min remaining_columns max_w)
198+ ImagePad (min_w,min_h) i -> do
199+ let hpad = if image_width i < min_w
200+ then background_fill (min_w - image_width i) (image_height i)
201+ else empty_image
202+ let vpad = if image_height i < min_h
203+ then background_fill (image_width i) (min_h - image_height i)
204+ else empty_image
205+ ops_for_row mrow_ops bg region ((i <|> hpad) <-> vpad) skip_dim y remaining_columns
155
206
156snoc_text_span :: (Foldable.Foldable t)
207snoc_text_span :: (Foldable.Foldable t)
157 => Attr
208 => Attr
158 -> t Char
209 -> t Char
159− -> Word -- width of text span in output display columns
160− -> Word -- width of text span in characters
161 -> MRowOps s
210 -> MRowOps s
162 -> Word
211 -> Word
163 -> Word
212 -> Word
213+ -> Word
164− -> ST s ()
214+ -> ST s Word
165−snoc_text_span a text_str ow cw mrow_ops y remaining_columns = do
215+snoc_text_span a text_str mrow_ops skip_col y remaining_columns = do
166− if ow > remaining_columns
167− then do
168− snoc_op mrow_ops y $ AttributeChange a
216+ snoc_op mrow_ops y $ AttributeChange a
169− let (ow', cw', txt) = Foldable.foldl'
217+ let (ow', dw', cw', txt) = Foldable.foldl'
170− build_cropped_txt
218+ build_cropped_txt
171− ( 0, 0, B.empty )
219+ ( 0, 0, 0, B.empty )
172− text_str
220+ text_str
173− snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
221+ snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
174− else do
222+ return $ skip_col - dw'
175− snoc_op mrow_ops y $ AttributeChange a
176− let utf8_bs = ({-# SCC "BSUTF8.fromString" #-} BSUTF8.fromString) $ Foldable.foldMap (\c -> [c]) text_str
177− snoc_op mrow_ops y $ TextSpan ow cw (UTF8.fromRep utf8_bs)
178 where
223 where
179− build_cropped_txt (ow', char_count', b0) c = {-# SCC "build_cropped_txt" #-}
224+ build_cropped_txt (ow', dw', char_count', b0) c = {-# SCC "build_cropped_txt" #-}
180 let w = wcwidth c
225 let w = wcwidth c
181 -- Characters with unknown widths occupy 1 column.
226 -- Characters with unknown widths occupy 1 column.
182 --
227 --
@@ -185,9 +230,18 @@ snoc_text_span a text_str ow cw mrow_ops y remaining_columns = do
185 -- the character. If so then this replacement process may need to be implemented
230 -- the character. If so then this replacement process may need to be implemented
186 -- manually for consistent behavior across terminals.
231 -- manually for consistent behavior across terminals.
187 w' = toEnum $ if w < 0 then 1 else w
232 w' = toEnum $ if w < 0 then 1 else w
233+ in if dw' == skip_col
234+ then if ow' == remaining_columns
235+ then ( ow', dw', char_count', b0 )
188− in if (w' + ow') > remaining_columns
236+ else if (w' + ow') > remaining_columns
189− then ( ow', char_count', b0 )
237+ then ( remaining_columns, dw', char_count' + ooverflow, B.append b0 $ B.pack $ encode $ genericReplicate ooverflow '…' )
190− else ( ow' + w', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
238+ else ( ow' + w', dw', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
239+ else if (w' + dw') > skip_col
240+ then ( doverflow, skip_col, doverflow, B.append b0 $ B.pack $ encode $ genericReplicate doverflow '…' )
241+ else ( ow', w' + dw', char_count', b0 )
242+ where
243+ doverflow = skip_col - dw'
244+ ooverflow = remaining_columns - ow'
191
245
192snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
246snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
193snoc_bg_fill _row_ops _bg 0 _row
247snoc_bg_fill _row_ops _bg 0 _row