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 bitmap_header
[x
] = fileReader
.read
(1)[0].ascii
128 self.file_size
= get_value
(bitmap_header
.subarray
(2, 4))
129 self.data_offset
= get_value
(bitmap_header
.subarray
(10, 4))
131 # =============== DIB header ================
133 dib_header
[x
] = fileReader
.read
(1)[0].ascii
135 var dib_size
= get_value
(dib_header
.subarray
(0, 4))
136 # only support BITMAPINFOHEADER
137 if dib_size
!= 40 then
138 print
"This type of bitmap is not supported"
143 self.width
= get_value
(dib_header
.subarray
(4, 4))
144 self.height
= get_value
(dib_header
.subarray
(8, 4))
145 self.bits_per_pixel
= get_value
(dib_header
.subarray
(14, 2))
146 self.image_size
= get_value
(dib_header
.subarray
(20, 4))
148 if self.bits_per_pixel
!= 24 then
149 print
"Only full color bitmaps are supported"
154 if self.bits_per_pixel
== 24 then
155 # assert self.image_size + 54 <= self.file_size
156 # start loading image data, for now assume no padding
157 for x
in [0..self.height
[
159 var row
= new Array[Int].with_capacity
(self.width
)
160 for y
in [0..self.width
[
162 var red
= fileReader
.read
(1)[0].ascii
* 256 * 256
163 var green
= fileReader
.read
(1)[0].ascii
* 256
164 var blue
= fileReader
.read
(1)[0].ascii
165 row
.add
(red
+ green
+ blue
)
171 end #end of load_from_file method
173 # Reads in a series of bytes from the FileReader when loading a Bitmap from a file
174 # FileReader.read(1) is used due to https://github.com/privat/nit/issues/1264
175 private fun read_chars
(fr
: FileReader, howMany
: Int): String
178 for x
in [1..howMany
]
185 # Converts the value contained in two or four bytes into an Int. Since Nit
186 # does not have a byte type, Int is used
187 private fun get_value
(array
: Array[Int]): Int
190 for x
in [0..array
.length
[ do
191 value
+= array
[x
] * 256.to_f
.pow
(x
.to_f
).to_i
196 # Converts the value in an Int to four bytes. Since Nit does not have a byte
198 private fun set_value
(array
: Array[Int], start_index
: Int, value
: Int)
200 array
[start_index
] = value
.bin_and
(0x000000FF)
201 array
[start_index
+ 1] = value
.rshift
(8).bin_and
(0x000000FF)
202 array
[start_index
+ 2] = value
.rshift
(16).bin_and
(0x000000FF)
203 array
[start_index
+ 3] = value
.rshift
(24).bin_and
(0x000000FF)
206 # Saves the bitmap into a file
207 fun save
(path
: String)
209 var fw
= new FileWriter.open
(path
)
210 # Write bitmap header
211 for x
in [0..self.bitmap_header
.length
[ do
212 fw
.write
(self.bitmap_header
[x
].ascii
.to_s
)
215 for x
in [0..self.dib_header
.length
[ do
216 fw
.write
(self.dib_header
[x
].ascii
.to_s
)
218 # Write color table (if any)
219 # Write data (no padding for now)
220 for x
in [0..self.height
[ do
221 var row
= self.data
[x
]
222 for y
in [0..self.width
[ do
224 var red
= pixel
.rshift
(16)
225 var green
= pixel
.bin_and
(0x00FF00).rshift
(8)
226 var blue
= pixel
.bin_and
(0x000000FF)
227 fw
.write
(red
.ascii
.to_s
)
228 fw
.write
(green
.ascii
.to_s
)
229 fw
.write
(blue
.ascii
.to_s
)
235 # Converts the bitmap to grayscale by manipulating each individual pixel
238 for x
in [0..self.height
[ do
239 var row
= self.data
[x
]
240 for y
in [0..self.width
[ do
242 var red
= pixel
.rshift
(16)
243 var green
= pixel
.bin_and
(0x00FF00).rshift
(8)
244 var blue
= pixel
.bin_and
(0x000000FF)
245 var lum
= (0.2126 * red
.to_f
+ 0.7152 * green
.to_f
+ 0.0722 * blue
.to_f
).to_i
246 pixel
= lum
* 256 * 256 + lum
* 256 + lum
247 self.data
[x
][y
] = pixel
252 # Sets an individual pixel, where position (0, 0) represents the top-left pixel.
253 fun set_pixel
(x
: Int, y
: Int, color
: Int)
255 if x
>= 0 and y
>= 0 and x
< self.width
and y
< self.height
then
256 # Since a bitmap stores its rows of pixels upside-down, y is mapped to
257 # height - 1 - y to make (0, 0) the top-left pixel
258 self.data
[self.height
- 1 - y
][x
] = color
261 end #end of class Bitmap