Data storage
One of the things VHDL can do (contrary to popular belief :)) is 2-D arrays. So reading images into a 2-D array is a very natural way to store the data. We’ll create a package called pgm
to keep all our image reading and writing code together:
package pgm is
subtype coordinate : natural;
subtype pixel is integer range 0 to 255;
type pixel_array is array (coordinate range <>, coordinate range <>) of pixel;
type pixel_array_ptr is access pixel_array;
-- Function: transpose
-- Useful for initialising arrays such that the first coordinate is the x-coord, but the initialisation can "look" like the
-- image in question within the code
function transpose (i : pixel_array) return pixel_array;
…
Some points:
- We’ve got a subtype for the raw pixel type – this enables us to change a single place if we decide to extend to (say) 16-bit pixels.
- Images are stored in a 2d array, with the x-coordinate as the first dimension, as is traditional (unless you use Matlab!)
- We’ll use an
access
type for the pixel storage being returned from the image reading functionpgm_read
to allow us to dynamically allocate the storage based on how big the actual image is, rather than having to know at compile-time. -
The
transpose
function is the best way I can come up with so far that allows us to create constants which represent images where the code looks like the image it represents. Without this an initialiser for a 4×2 image would look like this:constant c : pixel_array(0 to 3, 0 to 1) := ( (11, 12), (21, 22), (31, 32), (41, 42));
A bit counter-intuitive, no? With the
transpose
function we can do:constant c : pixel_array(0 to 3, 0 to 1) := transpose(pixel_array'( (11, 21, 31, 41),(12, 22, 32, 42));
API
The public API for playing with the images will be simple:
- a function to read an image from a file into an
image
record -
a function to write an
image
record out to a fileimpure function pgm_read (filename : string) return pixel_array_ptr;
procedure pgm_write (filename : in string; i : in pixel_array);
end package pgm;
Testing
For the input side, a simple testbench is all that is required, along with a carefully crafted PGM file for which we “know the right answers”. We can also create an image from scratch and write the new image out. Checking that final image will have to be done by hand. We could “round-trip” the image back in through our image reading function, but this may mask any errors we’ve made in the reading and writing functions which happen to cancel each other out!
entity tb_pgm is
end entity tb_pgm;
use work.libv.all;
use work.pgm.all;
architecture test of tb_pgm is
begin -- architecture test
test1 : process is
variable i : pixel_array_ptr;
constant testdata : pixel_array(0 to 7, 0 to 3) := transpose(pixel_array'(
(000, 027, 062, 095, 130, 163, 198, 232),
(000, 000, 000, 000, 000, 000, 000, 000),
(255, 255, 255, 255, 255, 255, 255, 255),
(100, 100, 100, 100, 100, 255, 255, 255))
);
variable blacksquare : pixel_array(0 to 7, 0 to 7) := (others => (others => 0));
begin -- process test1
-- test on a proper image
i := pgm_read("testimage_ascii.pgm");
assert i /= null report "pixels are null" severity error;
assert_equal("ASCII PGM Width", i.all'length(1), 8);
assert_equal("ASCII PGM Height", i.all'length(2), 4);
assert_equal("ASCII PGM data", i.all, testdata);
-- make sure we return a non-image for the binary-style PGM file
i := pgm_read("testimage.pgm");
assert i = null report "Binary pixels should be null" severity error;
-- Now create an image from scratch - a letter M
blacksquare(1,1) := 255; blacksquare(5,1) := 255;
blacksquare(1,2) := 255; blacksquare(2,2) := 255; blacksquare(4,2) := 255; blacksquare(5,2) := 255;
blacksquare(1,3) := 255; blacksquare(3,3) := 255; blacksquare(5,3) := 255;
blacksquare(1,4) := 255; blacksquare(5,4) := 255;
blacksquare(1,5) := 255; blacksquare(5,5) := 255;
blacksquare(1,6) := 255; blacksquare(5,6) := 255;
pgm_write("test_write.pgm", blacksquare);
report "End of tests" severity note;
wait;
end process test1;
end architecture test;
Aside (libv)
You can read about the non-standard looking assert_equal
functions here.
Back to the tests
In the spirit of Test-Driven Development, all the API elements are left empty, except for the pgm_read
function which has to return something. So it returns NULL
– an empty image! With the results:
pgm.vhd:204:9:@0ms:(assertion error): pixels are null
Hurrah! It fails in the way we’d hope!
Onwards to actually reading and writing…
hello sir ,
thank you for this informations ,and i want to know how i can read and modify a binary file (image satellitaire) in xilinx (vhdl) .
im a student PHd first year and my these about Implémentation FPGA sécurisée des algorithmes de
télédétection spatiale : optimisation au cas des CubeSats.