on 8e0c7cc help sign in
vty/src/Graphics/Vty/Image.hs
305 lines 12 kB history raw
1-- Copyright 2009-2010 Corey O'Connor
2{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE DisambiguateRecordFields #-}
5module Graphics.Vty.Image ( DisplayText
6 , Image
7 , imageWidth
8 , imageHeight
9 , horizJoin
10 , (<|>)
11 , vertJoin
12 , (<->)
13 , horizCat
14 , vertCat
15 , backgroundFill
16 , text
17 , text'
18 , char
19 , string
20 , iso10646String
21 , utf8String
22 , utf8Bytestring
23 , utf8Bytestring'
24 , charFill
25 , emptyImage
26 , safeWcwidth
27 , safeWcswidth
28 , wcwidth
29 , wcswidth
30 , crop
31 , cropRight
32 , cropLeft
33 , cropBottom
34 , cropTop
35 , pad
36 , resize
37 , resizeWidth
38 , resizeHeight
39 , translate
40 , translateX
41 , translateY
42 -- | The possible display attributes used in constructing an `Image`.
43 , module Graphics.Vty.Attributes
44 )
45 where
47import Graphics.Vty.Attributes
48import Graphics.Vty.Image.Internal
49import Graphics.Text.Width
51import qualified Data.ByteString as B
52import qualified Data.ByteString.Lazy as BL
53import qualified Data.Text as T
54import qualified Data.Text.Encoding as T
55import qualified Data.Text.Lazy as TL
56import qualified Data.Text.Lazy.Encoding as TL
57import Data.Word
59infixr 5 <|>
60infixr 4 <->
62-- | An area of the picture's bacground (See Background) of w columns and h rows.
63backgroundFill :: Int -> Int -> Image
64backgroundFill w h
65 | w == 0 = EmptyImage
66 | h == 0 = EmptyImage
67 | otherwise = BGFill w h
69-- | Combines two images horizontally. Alias for horizJoin
70--
71-- infixr 5
72(<|>) :: Image -> Image -> Image
73(<|>) = horizJoin
75-- | Combines two images vertically. Alias for vertJoin
76--
77-- infixr 4
78(<->) :: Image -> Image -> Image
79(<->) = vertJoin
81-- | Compose any number of images horizontally.
82horizCat :: [Image] -> Image
83horizCat = foldr horizJoin EmptyImage
85-- | Compose any number of images vertically.
86vertCat :: [Image] -> Image
87vertCat = foldr vertJoin EmptyImage
89-- | A Data.Text.Lazy value
90text :: Attr -> TL.Text -> Image
91text a txt
92 | TL.length txt == 0 = EmptyImage
93 | otherwise = let displayWidth = safeWcswidth (TL.unpack txt)
94 in HorizText a txt displayWidth (fromIntegral $! TL.length txt)
96-- | A Data.Text value
97text' :: Attr -> T.Text -> Image
98text' a txt
99 | T.length txt == 0 = EmptyImage
100 | otherwise = let displayWidth = safeWcswidth (T.unpack txt)
101 in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)
103-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
104-- the ISO-10646 encoding.
105char :: Attr -> Char -> Image
106char a c =
107 let displayWidth = safeWcwidth c
108 in HorizText a (TL.singleton c) displayWidth 1
110-- | A string of characters layed out on a single row with the same display attribute. The string is
111-- assumed to be a sequence of ISO-10646 characters.
112--
113-- Note: depending on how the Haskell compiler represents string literals a string literal in a
114-- UTF-8 encoded source file, for example, may be represented as a ISO-10646 string.
115-- That is, I think, the case with GHC 6.10. This means, for the most part, you don't need to worry
116-- about the encoding format when outputting string literals. Just provide the string literal
117-- directly to iso10646String or string.
118--
119iso10646String :: Attr -> String -> Image
120iso10646String a str =
121 let displayWidth = safeWcswidth str
122 in HorizText a (TL.pack str) displayWidth (length str)
124-- | Alias for iso10646String. Since the usual case is that a literal string like "foo" is
125-- represented internally as a list of ISO 10646 31 bit characters.
126--
127-- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while
128-- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime
129-- representation.
130string :: Attr -> String -> Image
131string = iso10646String
133-- | A string of characters layed out on a single row. The input is assumed to be the bytes for
134-- UTF-8 encoded text.
135utf8String :: Attr -> [Word8] -> Image
136utf8String a bytes = utf8Bytestring a (BL.pack bytes)
138-- | Renders a UTF-8 encoded lazy bytestring.
139utf8Bytestring :: Attr -> BL.ByteString -> Image
140utf8Bytestring a bs = text a (TL.decodeUtf8 bs)
142-- | Renders a UTF-8 encoded strict bytestring.
143utf8Bytestring' :: Attr -> B.ByteString -> Image
144utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)
146-- | creates a fill of the specified character. The dimensions are in number of characters wide and
147-- number of rows high.
148charFill :: Integral d => Attr -> Char -> d -> d -> Image
149charFill _a _c 0 _h = EmptyImage
150charFill _a _c _w 0 = EmptyImage
151charFill a c w h =
152 vertCat $ replicate (fromIntegral h) $ HorizText a txt displayWidth charWidth
153 where
154 txt = TL.replicate (fromIntegral w) (TL.singleton c)
155 displayWidth = safeWcwidth c * (fromIntegral w)
156 charWidth = fromIntegral w
158-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
159-- attributes.
160emptyImage :: Image
161emptyImage = EmptyImage
163-- | pad the given image. This adds background character fills to the left, top, right, bottom.
164-- The pad values are how many display columns or rows to add.
165pad :: Int -> Int -> Int -> Int -> Image -> Image
166pad 0 0 0 0 i = i
167pad inL inT inR inB inImage
168 | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"
169 | otherwise = go inL inT inR inB inImage
170 where
171 -- TODO: uh.
172 go 0 0 0 0 i = i
173 go 0 0 0 b i = VertJoin i (BGFill w b) w h
174 where w = imageWidth i
175 h = imageHeight i + b
176 go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
177 where w = imageWidth i + r
178 h = imageHeight i
179 go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
180 where w = imageWidth i
181 h = imageHeight i + t
182 go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
183 where w = imageWidth i + l
184 h = imageHeight i
186-- | translates an image by padding or cropping the top and left.
187--
188-- This can have an unexpected effect: Translating an image to less than (0,0) then to greater than
189-- (0,0) will crop the image.
190translate :: Int -> Int -> Image -> Image
191translate x y i = translateX x (translateY y i)
193-- | translates an image by padding or cropping the left
194translateX :: Int -> Image -> Image
195translateX x i
196 | x < 0 = let s = abs x in CropLeft i s (imageWidth i - s) (imageHeight i)
197 | x == 0 = i
198 | otherwise = let h = imageHeight i in HorizJoin (BGFill x h) i (imageWidth i + x) h
200-- | translates an image by padding or cropping the top
201translateY :: Int -> Image -> Image
202translateY y i
203 | y < 0 = let s = abs y in CropTop i s (imageWidth i) (imageHeight i - s)
204 | y == 0 = i
205 | otherwise = let w = imageWidth i in VertJoin (BGFill w y) i w (imageHeight i + y)
207-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right
208-- or bottom.
209--
210-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the
211-- right.
212crop :: Int -> Int -> Image -> Image
213crop 0 _ _ = EmptyImage
214crop _ 0 _ = EmptyImage
215crop w h i = cropBottom h (cropRight w i)
217-- | crop the display height. If the image is less than or equal in height then this operation has
218-- no effect. Otherwise the image is cropped from the bottom.
219cropBottom :: Int -> Image -> Image
220cropBottom 0 _ = EmptyImage
221cropBottom h inI
222 | h < 0 = error "cannot crop height to less than zero"
223 | otherwise = go inI
224 where
225 go EmptyImage = EmptyImage
226 go i@(CropBottom {croppedImage, outputWidth, outputHeight})
227 | outputHeight <= h = i
228 | otherwise = CropBottom croppedImage outputWidth h
229 go i
230 | h >= imageHeight i = i
231 | otherwise = CropBottom i (imageWidth i) h
233-- | ensure the image is no wider than the given width. If the image is wider then crop the right
234-- side.
235cropRight :: Int -> Image -> Image
236cropRight 0 _ = EmptyImage
237cropRight w inI
238 | w < 0 = error "cannot crop width to less than zero"
239 | otherwise = go inI
240 where
241 go EmptyImage = EmptyImage
242 go i@(CropRight {croppedImage, outputWidth, outputHeight})
243 | outputWidth <= w = i
244 | otherwise = CropRight croppedImage w outputHeight
245 go i
246 | w >= imageWidth i = i
247 | otherwise = CropRight i w (imageHeight i)
249-- | ensure the image is no wider than the given width. If the image is wider then crop the left
250-- side.
251cropLeft :: Int -> Image -> Image
252cropLeft 0 _ = EmptyImage
253cropLeft w inI
254 | w < 0 = error "cannot crop the width to less than zero"
255 | otherwise = go inI
256 where
257 go EmptyImage = EmptyImage
258 go i@(CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})
259 | outputWidth <= w = i
260 | otherwise =
261 let leftSkip' = leftSkip + outputWidth - w
262 in CropLeft croppedImage leftSkip' w outputHeight
263 go i
264 | imageWidth i <= w = i
265 | otherwise = CropLeft i (imageWidth i - w) w (imageHeight i)
267-- | crop the display height. If the image is less than or equal in height then this operation has
268-- no effect. Otherwise the image is cropped from the top.
269cropTop :: Int -> Image -> Image
270cropTop 0 _ = EmptyImage
271cropTop h inI
272 | h < 0 = error "cannot crop the height to less than zero"
273 | otherwise = go inI
274 where
275 go EmptyImage = EmptyImage
276 go i@(CropTop {croppedImage, topSkip, outputWidth, outputHeight})
277 | outputHeight <= h = i
278 | otherwise =
279 let topSkip' = topSkip + outputHeight - h
280 in CropTop croppedImage topSkip' outputWidth h
281 go i
282 | imageHeight i <= h = i
283 | otherwise = CropTop i (imageHeight i - h) (imageWidth i) h
285-- | Generic resize. Pads and crops as required to assure the given display width and height.
286-- This is biased to pad/crop the right and bottom.
287resize :: Int -> Int -> Image -> Image
288resize w h i = resizeHeight h (resizeWidth w i)
290-- | Resize the width. Pads and crops as required to assure the given display width.
291-- This is biased to pad/crop the right.
292resizeWidth :: Int -> Image -> Image
293resizeWidth w i = case w `compare` imageWidth i of
294 LT -> cropRight w i
295 EQ -> i
296 GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)
298-- | Resize the height. Pads and crops as required to assure the given display height.
299-- This is biased to pad/crop the bottom.
300resizeHeight :: Int -> Image -> Image
301resizeHeight h i = case h `compare` imageHeight i of
302 LT -> cropBottom h i
303 EQ -> i
304 GT -> i <-> BGFill (imageWidth i) (h - imageHeight i)