for y in 0...height
for x in 0...width
# BGR => RGB & miror y
z = (height-y-1)*width*3 + 3*x
corrected << data[z+2]
corrected << data[z+1]
corrected << data[z ]
corrected << 255 # Add alpha value
end
for x in width...real_size
corrected << 0
corrected << 0
corrected << 0
corrected << 0
end
end
for y in height...real_size
for x in 0...real_size
corrected << 0
corrected << 0
corrected << 0
corrected << 0
end
end
Refactorings
No refactoring yet !
Ben Marini
January 31, 2010, January 31, 2010 20:33, permalink
I'd like to help out, but I can't quite figure out what your code snippet is doing. Can you provide a more complete code snippet? For instance, define `height`, `width`, `real_size`, `data`, and `corrected`?
darkleo.myopenid.com
January 31, 2010, January 31, 2010 21:36, permalink
I read `data`, `width` and `height` directly from a bmp file.
To use it, OpenGL requires that the image size (on each dimension) be a power of 2.
So `real_size` is the closest power of 2 who match the image size.
The original data is encoded in BGR, and i need it in RGBA.
Finally i need to reverse the rows, because the data "begin from the end"
I give an example with a very simple bitmap, 3 rows, 2 columns :
Red/Red
Green/Green
Blue/Blue
With this 3*2 bitmap :
data = [ 255, 0, 0, # 1st pixel, blue (third row)
255, 0, 0, # 2st pixel, blue
0, 255, 0, # 3nd pixel, green (second row)
0, 255, 0, # 4nd pixel, green
0, 0, 255, # 5rd pixel, red (first row)
0, 0, 255, # 6rd pixel, red
]
width = 2
height = 3
We calculate :
real_size = 4 # closest power of 2
And we obtain a 4*4 bitmap :
corrected = [ 255, 0, 0, 255 # 1st pixel, red (first row)
255, 0, 0, 255 # 2st pixel, red
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 255, 0, 255 # 3nd pixel, green (second row)
0, 255, 0, 255 # 4nd pixel, green
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 255, 255 # 5rd pixel, blue (third row)
0, 0, 255, 255 # 6rd pixel, blue
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty (last row)
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
0, 0, 0, 0 # Empty
]
I give also the entire method.
Thanks a lot by advance !
def self.load_bmp name
fail unless FileTest.exist?('Media/'+name)
file = File.open('Media/'+name, 'rb')
struct = {:name => name}
file.seek(18, IO::SEEK_CUR)
# Read width and height
struct[:width] = file.read(4).unpack('i')[0]
struct[:height] = file.read(4).unpack('i')[0]
# Calculate closest power of 2
struct[:real_size] = 2
struct[:real_size] *= 2 while struct[:real_size] < struct[:width]
struct[:real_size] *= 2 while struct[:real_size] < struct[:height]
file.seek(28, IO::SEEK_CUR)
# Read data from file
size = struct[:width]*struct[:height]*3
data = file.read(size).unpack('C*')
file.close
corrected = []
# BGR => RGB & miror y
for y in 0...(struct[:height])
for x in 0...(struct[:width])
z = (struct[:height]-y-1)*struct[:width]*3 + 3*x
corrected << data[z+2]
corrected << data[z+1]
corrected << data[z ]
corrected << 255 # Add alpha value
end
for x in struct[:width]...struct[:real_size]
corrected << 0
corrected << 0
corrected << 0
corrected << 0
end
end
for y in struct[:height]...struct[:real_size]
for x in 0...struct[:real_size]
corrected << 0
corrected << 0
corrected << 0
corrected << 0
end
end
# Return corrected texture
struct[:data] = corrected.pack('C*')
return struct
end
Ants
February 1, 2010, February 01, 2010 23:48, permalink
OpenGL 2.0 and up supports textures that are not powers of two.
Ben Marini
February 3, 2010, February 03, 2010 16:09, permalink
Here's my functional version, with some test code.
module Bitmap
def self.bgr_to_rgba(raw_bgr_array, width, height, new_dimension)
width_padding = new_dimension - width
height_padding = new_dimension - height
# First convert to array of Pixel::BGR objects
raw_bgr_array.enum_for(:each_slice, 3).map { |a| Pixel::BGR.new(*a) }.
reverse. # Reverse it
map { |bgr| bgr.to_rgba }. # Map to array of Pixel::RGBA objects
enum_for(:each_slice, width).inject([]) { |memo, row| memo << row }. # Create a 2d array based on width
map { |row|
row.concat( [Pixel::RGBA.blank] * width_padding )
}.concat( [ [Pixel::RGBA.blank] * new_dimension ] * height_padding ). # Change dimensions of 2d array, adding blanks
flatten.map { |rgba| rgba.to_a }.flatten # Flatten everything back into an array of integers
end
module Pixel
class RGBA < Struct.new(:red, :green, :blue, :alpha)
def self.blank
new(0,0,0,0)
end
def to_a
[red, green, blue, alpha]
end
end
class BGR < Struct.new(:blue, :green, :red)
def to_rgba
RGBA.new(red, green, blue, 255)
end
end
end
end
if __FILE__ == $0
require "test/unit"
class TestBgrToRgba < Test::Unit::TestCase
def test_bgr_to_rgba
width = 2
height = 3
real_size = 4 # closest power of 2
data = [
255, 0, 0, # 1st pixel, blue (third row)
255, 0, 0, # 2st pixel, blue
0, 255, 0, # 3nd pixel, green (second row)
0, 255, 0, # 4nd pixel, green
0, 0, 255, # 5rd pixel, red (first row)
0, 0, 255, # 6rd pixel, red
]
corrected = [
255, 0, 0, 255, # 1st pixel, red (first row)
255, 0, 0, 255, # 2st pixel, red
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 255, 0, 255, # 3nd pixel, green (second row)
0, 255, 0, 255, # 4nd pixel, green
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 255, 255, # 5rd pixel, blue (third row)
0, 0, 255, 255, # 6rd pixel, blue
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty (last row)
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 0, 0 # Empty
]
result = Bitmap.bgr_to_rgba(data, width, height, real_size)
assert_equal corrected, result
end
end
end
Ben Marini
February 3, 2010, February 03, 2010 16:09, permalink
Here's my functional version: http://gist.github.com/293713
darkleo.myopenid.com
February 7, 2010, February 07, 2010 21:44, permalink
@Ants : It is obviously much faster with powers of two, at least with ruby-opengl
@Ben Marini : Thank you very much, It will help me a lot, particularly the methods 'map' and 'flatten'
Alex Baranosky
February 11, 2010, February 11, 2010 02:04, permalink
Mine is using Ruby 1.9... I refactored all the code even the stuff loading the bmp from the file. I was not able to test any of that code however so hopefully it is bug free, the spec I show below covers generating the corrected bmp.
class BmpCorrector
def load_bmp(name)
bmp, original_bmp_data_array = create_bmp_from_file(name)
bmp.generate_corrected(original_bmp_data_array)
end
def create_bmp_from_file(name)
file_name = "Media/#{name}"
fail unless FileTest.exist?(file_name)
bmp = nil
original_bmp_data_array = open(file_name, 'rb') do |file|
width, height = read_width_and_height(file)
bmp = Bmp.new(height, width)
read_data(height, width, file)
end
return bmp, original_bmp_data_array
end
def read_width_and_height(file)
file.seek(18, IO::SEEK_CUR)
[file.read(4).unpack('i')[0], file.read(4).unpack('i')[0]]
end
def read_data(height, width, file)
file.seek(28, IO::SEEK_CUR)
size = width * height * Bmp::BGR_PIXEL_SLICE_WIDTH
file.read(size).unpack('C*')
end
end
class Bmp
BGR_PIXEL_SLICE_WIDTH = 3
def initialize(height, width)
@original_height, @original_width = height, width
@new_dimension = closest_power_of_two
end
def generate_corrected(raw_bgr_array)
right_padding = @new_dimension - @original_width
bottom_padding = @new_dimension - @original_height
pixels = pixels_from_raw_bgr_data(raw_bgr_array)
pixel_rows_w_out_padding = pixel_2d_array(pixels)
add_padding(pixel_rows_w_out_padding, bottom_padding, right_padding)
end
def pixels_from_raw_bgr_data(raw_bgr_array)
raw_bgr_array.each_slice(BGR_PIXEL_SLICE_WIDTH).
collect { |slice| Pixel.from_bgr(*slice) }.
reverse
end
def pixel_2d_array(pixels)
pixels.each_slice(@original_width).inject([]) { |result, row| result << row }
end
def add_padding(pixel_rows, bottom_padding, right_padding)
corrected_array = pixel_rows.collect {|row| row + Pixel.blanks(right_padding) }
corrected_array += Pixel.blanks(@new_dimension * bottom_padding)
corrected_array.flatten.collect(&:to_a).flatten
end
def closest_power_of_two
result = 2
result *= 2 while (result < @original_height || result < @original_width)
result
end
end
class Pixel
def initialize(red = 0, green = 0, blue = 0, alpha = 0)
@red, @green, @blue, @alpha = red, green, blue, alpha
end
def self.from_bgr(blue, green, red)
Pixel.new(red, green, blue, 255)
end
def self.blanks(amount = 1)
Array.new(amount) { Pixel.new }
end
def to_a
[@red, @green, @blue, @alpha]
end
end
BmpCorrector.new.load_bmp("filename.....")
require "spec"
require 'corrector'
describe Bmp do
width = 2
height = 3
data = [
255, 0, 0, # 1st pixel, blue (third row)
255, 0, 0, # 2st pixel, blue
0, 255, 0, # 3nd pixel, green (second row)
0, 255, 0, # 4nd pixel, green
0, 0, 255, # 5rd pixel, red (first row)
0, 0, 255, # 6rd pixel, red
]
expected_corrected = [
255, 0, 0, 255, # 1st pixel, red (first row)
255, 0, 0, 255, # 2st pixel, red
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 255, 0, 255, # 3nd pixel, green (second row)
0, 255, 0, 255, # 4nd pixel, green
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 255, 255, # 5rd pixel, blue (third row)
0, 0, 255, 255, # 6rd pixel, blue
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty (last row)
0, 0, 0, 0, # Empty
0, 0, 0, 0, # Empty
0, 0, 0, 0 # Empty
]
it "should correct the bmp properly" do
actual_corrected = Bmp.new(height, width).generate_corrected(data)
actual_corrected.should == expected_corrected
end
end
I need to reformat .bmp data, by adding a lot of '0'
But my code is... horribly ugly.
'data' contain the original data (array)
'corrected' is an empty array
Before process :
[XXXX
XXXX
XXXX]
After process :
[XXXX0000
XXXX0000
XXXX0000
00000000
00000000
00000000]