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:
21 # var bm1 = new Bitmap.load(path_to_a_bmp_file)
22 # var bm2 = new Bitmap.with_size(400, 300)
25 # The width and height attributes contain the image's width and height,
28 # Individual pixels can be manipulated by calling the set_pixel function:
31 # set_pixel(x, y, color)
34 # The no-argument grayscale function converts the bitmap to grayscale and is an
35 # implementation of this Rossetacode task:
36 # http://rosettacode.org/wiki/Grayscale_image
38 # Finally, the bitmap can be saved to a file by calling save:
41 # save(path_to_a_file)
44 # For information on the bitmap format, see
45 # http://en.wikipedia.org/wiki/BMP_file_format
47 # A module containing classes for manipulating bitmap images
50 # A class that represents a Bitmap image
52 # The file path if this Bitmap is created by loading a bitmap file
53 var file_path
: String is noinit
55 # The file extension if this Bitmap is created by loading a bitmap file
56 var file_extension
: String is noinit
58 # The file size if this Bitmap is created by loading a bitmap file
59 var file_size
: Int is noinit
61 # The image width in pixels
62 var width
: Int is noinit
64 # The image height in pixels
65 var height
: Int is noinit
67 # The offset of image data. Typically, 54
68 private var data_offset
: Int is noinit
70 # The number of bits representing a pixel. Currently only 24-bit bitmaps are supported
71 private var bits_per_pixel
: Int is noinit
73 # The size of images. For a 24-bit bitmap, this is equal to (3*width*height)
74 private var image_size
: Int is noinit
76 # 14-byte bitmap header
77 private var bitmap_header
= [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0]
80 private var dib_header
= [40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81 0, 0, 1, 0, 24, 0, 0, 0, 0, 0,
82 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
83 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
85 # The image data, each cell contains a pixel.
86 # The first 8 least-significant bits represent the red component,
87 # the next 8 least-significant bits represent the green component,
88 # and the next 8 bit represent the blue component.
89 # The last 8 most-significant bits are not used
90 private var data
= new Array[Array[Int]]
92 # Constructs a Bitmap object by passing the image's width and height (in pixels)
93 init with_size
(width
: Int, height
: Int)
97 self.bits_per_pixel
= 24
101 self.set_value
(dib_header
, 4, width
)
102 self.set_value
(dib_header
, 8, height
)
104 var file_size
= 3 * width
* height
+ 54
105 self.set_value
(bitmap_header
, 2, file_size
)
108 self.data
= new Array[Array[Int]].with_capacity
(height
)
109 for x
in [0..height
[ do
110 var row
= new Array[Int].with_capacity
(width
)
111 for y
in [0..width
[ do
118 # Creates an instance of Bitmap by loading an existing image file
119 init load
(path
: String)
121 self.file_path
= path
122 var temp
= path
.file_extension
124 self.file_extension
= temp
126 self.file_extension
= ""
128 var fileReader
= new FileReader.open
(path
)
130 # =============== Bitmap header ================
132 var b
= fileReader
.read_byte
136 self.file_size
= get_value
(bitmap_header
.subarray
(2, 4))
137 self.data_offset
= get_value
(bitmap_header
.subarray
(10, 4))
139 # =============== DIB header ================
141 var b
= fileReader
.read_byte
145 var dib_size
= get_value
(dib_header
.subarray
(0, 4))
146 # only support BITMAPINFOHEADER
147 if dib_size
!= 40 then
148 print
"This type of bitmap is not supported"
153 self.width
= get_value
(dib_header
.subarray
(4, 4))
154 self.height
= get_value
(dib_header
.subarray
(8, 4))
155 self.bits_per_pixel
= get_value
(dib_header
.subarray
(14, 2))
156 self.image_size
= get_value
(dib_header
.subarray
(20, 4))
158 if self.bits_per_pixel
!= 24 then
159 print
"Only full color bitmaps are supported"
164 if self.bits_per_pixel
== 24 then
165 # assert self.image_size + 54 <= self.file_size
166 # start loading image data, for now assume no padding
167 for x
in [0..self.height
[
169 var row
= new Array[Int].with_capacity
(self.width
)
170 var rgb_str
= new CString(3)
171 for y
in [0..self.width
[
173 var bts
= fileReader
.read_bytes_to_cstring
(rgb_str
, 3)
174 if bts
< 3 then return
175 var red
= rgb_str
[0] << 16
176 var green
= rgb_str
[1] << 8
177 var blue
= rgb_str
[2]
178 row
.add
(red
.to_i
+ green
.to_i
+ blue
.to_i
)
184 end #end of load_from_file method
186 # Converts the value contained in two or four bytes into an Int. Since Nit
187 # does not have a byte type, Int is used
188 private fun get_value
(array
: Array[Int]): Int
191 for x
in [0..array
.length
[ do
192 value
+= array
[x
] * 256.to_f
.pow
(x
.to_f
).to_i
197 # Converts the value in an Int to four bytes. Since Nit does not have a byte
199 private fun set_value
(array
: Array[Int], start_index
: Int, value
: Int)
201 array
[start_index
] = value
& 0x000000FF
202 array
[start_index
+ 1] = (value
>> 8) & 0x000000FF
203 array
[start_index
+ 2] = (value
>> 16) & 0x000000FF
204 array
[start_index
+ 3] = (value
>> 24) & 0x000000FF
207 # Saves the bitmap into a file
208 fun save
(path
: String)
210 var fw
= new FileWriter.open
(path
)
211 # Write bitmap header
212 for x
in [0..self.bitmap_header
.length
[ do
213 fw
.write
(self.bitmap_header
[x
].code_point
.to_s
)
216 for x
in [0..self.dib_header
.length
[ do
217 fw
.write
(self.dib_header
[x
].code_point
.to_s
)
219 # Write color table (if any)
220 # Write data (no padding for now)
221 for x
in [0..self.height
[ do
222 var row
= self.data
[x
]
223 for y
in [0..self.width
[ do
225 var red
= pixel
>> 16
226 var green
= (pixel
& 0x00FF00) >> 8
227 var blue
= pixel
& 0x000000FF
228 fw
.write
(red
.code_point
.to_s
)
229 fw
.write
(green
.code_point
.to_s
)
230 fw
.write
(blue
.code_point
.to_s
)
236 # Converts the bitmap to grayscale by manipulating each individual pixel
239 for x
in [0..self.height
[ do
240 var row
= self.data
[x
]
241 for y
in [0..self.width
[ do
243 var red
= pixel
>> 16
244 var green
= (pixel
& 0x00FF00) >> 8
245 var blue
= pixel
& 0x000000FF
246 var lum
= (0.2126 * red
.to_f
+ 0.7152 * green
.to_f
+ 0.0722 * blue
.to_f
).to_i
247 pixel
= lum
* 256 * 256 + lum
* 256 + lum
248 self.data
[x
][y
] = pixel
253 # Sets an individual pixel, where position (0, 0) represents the top-left pixel.
254 fun set_pixel
(x
: Int, y
: Int, color
: Int)
256 if x
>= 0 and y
>= 0 and x
< self.width
and y
< self.height
then
257 # Since a bitmap stores its rows of pixels upside-down, y is mapped to
258 # height - 1 - y to make (0, 0) the top-left pixel
259 self.data
[self.height
- 1 - y
][x
] = color
262 end #end of class Bitmap