1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2015 Budi Kurniawan <budi2020@gmail.com>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # The Bitmap class represents a 24-bit bitmap image. An instance can be constructed
18 # by calling load or with_size:
20 # new Bitmap.load(path_to_a_bmp_file)
21 # new Bitmap.with_size(400, 300)
23 # The width and height attributes contain the image's width and height,
26 # Individual pixels can be manipulated by calling the set_pixel function:
28 # set_pixel(x, y, color)
30 # The no-argument grayscale function converts the bitmap to grayscale and is an
31 # implementation of this Rossetacode task:
32 # http://rosettacode.org/wiki/Grayscale_image
34 # Finally, the bitmap can be saved to a file by calling save:
36 # save(path_to_a_file)
38 # For information on the bitmap format, see
39 # http://en.wikipedia.org/wiki/BMP_file_format
41 # A module containing classes for manipulating bitmap images
44 # A class that represents a Bitmap image
46 # The file path if this Bitmap is created by loading a bitmap file
47 var file_path
: String is noinit
49 # The file extension if this Bitmap is created by loading a bitmap file
50 var file_extension
: String is noinit
52 # The file size if this Bitmap is created by loading a bitmap file
53 var file_size
: Int is noinit
55 # The image width in pixels
56 var width
: Int is noinit
58 # The image height in pixels
59 var height
: Int is noinit
61 # The offset of image data. Typically, 54
62 private var data_offset
: Int is noinit
64 # The number of bits representing a pixel. Currently only 24-bit bitmaps are supported
65 private var bits_per_pixel
: Int is noinit
67 # The size of images. For a 24-bit bitmap, this is equal to (3*width*height)
68 private var image_size
: Int is noinit
70 # 14-byte bitmap header
71 private var bitmap_header
: Array[Int] = [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0]
74 private var dib_header
: Array[Int] = [40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
75 0, 0, 1, 0, 24, 0, 0, 0, 0, 0,
76 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
79 # The image data, each cell contains a pixel.
80 # The first 8 least-significant bits represent the red component,
81 # the next 8 least-significant bits represent the green component,
82 # and the next 8 bit represent the blue component.
83 # The last 8 most-significant bits are not used
84 private var data
= new Array[Array[Int]]
86 # Constructs a Bitmap object by passing the image's width and height (in pixels)
87 init with_size
(width
: Int, height
: Int)
91 self.bits_per_pixel
= 24
95 self.set_value
(dib_header
, 4, width
)
96 self.set_value
(dib_header
, 8, height
)
98 var file_size
= 3 * width
* height
+ 54
99 self.set_value
(bitmap_header
, 2, file_size
)
102 self.data
= new Array[Array[Int]].with_capacity
(height
)
103 for x
in [0..height
[ do
104 var row
= new Array[Int].with_capacity
(width
)
105 for y
in [0..width
[ do
112 # Creates an instance of Bitmap by loading an existing image file
113 init load
(path
: String)
115 self.file_path
= path
116 var temp
= path
.file_extension
118 self.file_extension
= temp
120 self.file_extension
= ""
122 var fileReader
= new FileReader.open
(path
)
124 # =============== Bitmap header ================
126 var b
= fileReader
.read_byte
132 self.file_size
= get_value
(bitmap_header
.subarray
(2, 4))
133 self.data_offset
= get_value
(bitmap_header
.subarray
(10, 4))
135 # =============== DIB header ================
137 var b
= fileReader
.read_byte
138 if b
== null then return
141 var dib_size
= get_value
(dib_header
.subarray
(0, 4))
142 # only support BITMAPINFOHEADER
143 if dib_size
!= 40 then
144 print
"This type of bitmap is not supported"
149 self.width
= get_value
(dib_header
.subarray
(4, 4))
150 self.height
= get_value
(dib_header
.subarray
(8, 4))
151 self.bits_per_pixel
= get_value
(dib_header
.subarray
(14, 2))
152 self.image_size
= get_value
(dib_header
.subarray
(20, 4))
154 if self.bits_per_pixel
!= 24 then
155 print
"Only full color bitmaps are supported"
160 if self.bits_per_pixel
== 24 then
161 # assert self.image_size + 54 <= self.file_size
162 # start loading image data, for now assume no padding
163 for x
in [0..self.height
[
165 var row
= new Array[Int].with_capacity
(self.width
)
166 for y
in [0..self.width
[
168 var bts
= fileReader
.read_bytes
(3)
169 if bts
.length
!= 3 then return
170 var red
= bts
[0] << 16
171 var green
= bts
[1] << 8
173 row
.add
(red
+ green
+ blue
)
179 end #end of load_from_file method
181 # Converts the value contained in two or four bytes into an Int. Since Nit
182 # does not have a byte type, Int is used
183 private fun get_value
(array
: Array[Int]): Int
186 for x
in [0..array
.length
[ do
187 value
+= array
[x
] * 256.to_f
.pow
(x
.to_f
).to_i
192 # Converts the value in an Int to four bytes. Since Nit does not have a byte
194 private fun set_value
(array
: Array[Int], start_index
: Int, value
: Int)
196 array
[start_index
] = value
.bin_and
(0x000000FF)
197 array
[start_index
+ 1] = value
.rshift
(8).bin_and
(0x000000FF)
198 array
[start_index
+ 2] = value
.rshift
(16).bin_and
(0x000000FF)
199 array
[start_index
+ 3] = value
.rshift
(24).bin_and
(0x000000FF)
202 # Saves the bitmap into a file
203 fun save
(path
: String)
205 var fw
= new FileWriter.open
(path
)
206 # Write bitmap header
207 for x
in [0..self.bitmap_header
.length
[ do
208 fw
.write
(self.bitmap_header
[x
].ascii
.to_s
)
211 for x
in [0..self.dib_header
.length
[ do
212 fw
.write
(self.dib_header
[x
].ascii
.to_s
)
214 # Write color table (if any)
215 # Write data (no padding for now)
216 for x
in [0..self.height
[ do
217 var row
= self.data
[x
]
218 for y
in [0..self.width
[ do
220 var red
= pixel
.rshift
(16)
221 var green
= pixel
.bin_and
(0x00FF00).rshift
(8)
222 var blue
= pixel
.bin_and
(0x000000FF)
223 fw
.write
(red
.ascii
.to_s
)
224 fw
.write
(green
.ascii
.to_s
)
225 fw
.write
(blue
.ascii
.to_s
)
231 # Converts the bitmap to grayscale by manipulating each individual pixel
234 for x
in [0..self.height
[ do
235 var row
= self.data
[x
]
236 for y
in [0..self.width
[ do
238 var red
= pixel
.rshift
(16)
239 var green
= pixel
.bin_and
(0x00FF00).rshift
(8)
240 var blue
= pixel
.bin_and
(0x000000FF)
241 var lum
= (0.2126 * red
.to_f
+ 0.7152 * green
.to_f
+ 0.0722 * blue
.to_f
).to_i
242 pixel
= lum
* 256 * 256 + lum
* 256 + lum
243 self.data
[x
][y
] = pixel
248 # Sets an individual pixel, where position (0, 0) represents the top-left pixel.
249 fun set_pixel
(x
: Int, y
: Int, color
: Int)
251 if x
>= 0 and y
>= 0 and x
< self.width
and y
< self.height
then
252 # Since a bitmap stores its rows of pixels upside-down, y is mapped to
253 # height - 1 - y to make (0, 0) the top-left pixel
254 self.data
[self.height
- 1 - y
][x
] = color
257 end #end of class Bitmap