четверг, 14 февраля 2013 г.

Мои пояснение к Главе 1 книги Р. Д. Кларка. Конвертируем bitmap в массив цветов. FreeBasic.

Так как в первой главе не рассмотрен подробно вопрос о том, как создавать файл с массивом, содержащим изображение титульного экрана, необходимо исправить данноый момент. В результате этого пояснения мы напишем программу на FreeBasic которая будет загружать bmp файл с изображением и сохранять его в файл с массивом цветов, для последующего вывода в нашей программе.

0. Ставим FreeBasic

Так как в моем репозитории free basic-а не оказалось, то идем на страницу загрузки официального сайта (http://www.freebasic.net/get), качаем бинарники (чтобы меньше мороки) и устанавливаем:
$ wget -c http://downloads.sourceforge.net/fbc/FreeBASIC-0.24.0-linux.tar.gz
...
$ tar -xvf ./FreeBASIC-0.24.0-linux.tar.gz
...
$ cd FreeBASIC-0.24.0-linux/
$ sudo ./install.sh -i
FreeBASIC compiler successfully installed in /usr/local
проверяем:
$ fbc -version
FreeBASIC Compiler - Version 0.24.0 (08-19-2012) for linux
Copyright (C) 2004-2012 The FreeBASIC development team.
objinfo (libbfd 217)
все ОК. приступаем к реализации задачи.

1. Простейший случай (описанный в книге)

Для получения результата описанного в книге, проделаем все тоже самое, т.е. по пути наименьшего сопротивления

Задаем константы и подключаем модуль работы с графикой, объявляем переменные
#Include "fbgfx.bi"

'Высота и ширина нашей картинки
#define BMP_WIDTH 80
#define BMP_HEIGHT 60

#define BMP_FILENAME "title.bmp" 'Имя файла BMP изображения
#define BAS_FILENAME "title.bi" 'Имя выходного файла с массивом

DIM buffer AS ANY PTR 'указатель на облость памяти для хранения изображения
DIM as INTEGER x,y
Dim title(BMP_WIDTH*BMP_HEIGHT) As UInteger
Dim As Integer fp
Инициализируем графику и загрузим BMP изображение.
ScreenRes 640, 480, 16 'Создадим графическое окно приложения
buffer = IMAGECREATE(BMP_WIDTH, BMP_HEIGHT) 'Выделим память под изображение
BLOAD BMP_FILENAME, buffer 'Загрузим изображение в память
Выведем ее на экран
SCREENLOCK
PUT (0,0), buffer
SCREENUNLOCK
Прочитаем цвета пикселей экрана в массив
FOR y=0 to BMP_HEIGHT-1
  FOR x=0 to BMP_WIDTH-1
    title(x+y*BMP_WIDTH)=Point(x,y)
  NEXT x
NEXT y
Сохраним массив цветов на диск в виде FreeBasic кода
fp = FreeFile()  'Поучим свободный указатель на файл
Open BAS_FILENAME For Output As #fp  'Откроем файл на запись (если файл существует - данные перезапишутся)

'Запишем в файл заголовок. инструкции инициализирующие массив
PRINT #fp, "#Define titlew "; BMP_WIDTH
PRINT #fp, "#Define titleh "; BMP_HEIGHT
PRINT #fp, "Dim Shared title(";(BMP_WIDTH * BMP_HEIGHT);") As UInteger = { _"
'Добавим значения цветов
FOR y=0 to BMP_HEIGHT-1
  FOR x=0 to BMP_WIDTH-1
    PRINT #fp, title(x+y*BMP_WIDTH);
    IF y<(BMP_HEIGHT-1) OR x<(BMP_WIDTH-1) THEN PRINT #fp,", ";
  NEXT x
  PRINT #fp, " _"
NEXT y
' Запишем последнюю строку - завершение инициализации массива
PRINT #fp, "}"
Close #fp 'Закроем файл

полный код программы:
/'****************************************************************************
*
* Name: bmp_to_bas.bas
*
* Description: Demo for chapter 1 of Richard D. Clark wikibook 
*              "Let's Build a Roguelike"
*
* Synopsys:  BMP image to bas source converter
*
* Copyright 2013, Kanstansin A. Ashuika (aka Uvadzucumi, Fantik, etc...)
*
*                          The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies. 
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/
#Include "fbgfx.bi"

'BMP Image Width and Height
#define BMP_WIDTH 80
#define BMP_HEIGHT 60

#define BMP_FILENAME "title.bmp" 'Input bmp image file name
#define BAS_FILENAME "title.bi" 'Output bas file name

DIM buffer AS ANY PTR 'Pointer for load bmp image
DIM as INTEGER x,y
DIM title(BMP_WIDTH * BMP_HEIGHT) AS UINTEGER
DIM AS INTEGER fp

SCREENRES 640, 480, 16 'Create graphics window

buffer = IMAGECREATE(BMP_WIDTH, BMP_HEIGHT) 'Allocate memory for image buffer

BLOAD BMP_FILENAME, buffer 'Load image

'Block screen and draw image
SCREENLOCK
PUT (0,0), buffer
SCREENUNLOCK

'read pixel colors to array
FOR y=0 to BMP_HEIGHT-1
  FOR x=0 to BMP_WIDTH-1
    title(x+y*BMP_WIDTH)=Point(x,y)
  NEXT x
NEXT y

PRINT "Press any key to continue..."
SLEEP

'free image buffer
IMAGEDESTROY buffer

SCREEN 0 'Text mode

fp = FREEFILE()  'Get free file handle
OPEN BAS_FILENAME FOR OUTPUT AS #fp 'Open file for write

'Write first strings
PRINT #fp, "'Useage: (uinteger) pixel = title(x + y * titlew)"
PRINT #fp, "#Define titlew "; BMP_WIDTH
PRINT #fp, "#Define titleh "; BMP_HEIGHT
PRINT #fp, "Dim Shared title(";(BMP_WIDTH * BMP_HEIGHT);") As UInteger = { _"

FOR y=0 to BMP_HEIGHT-1
  FOR x=0 to BMP_WIDTH-1
    PRINT #fp, title(x+y*BMP_WIDTH);
    IF y<(BMP_HEIGHT-1) OR x<(BMP_WIDTH-1) THEN PRINT #fp,", ";
  NEXT x
  PRINT #fp, " _"
NEXT y
' Write last string
PRINT #fp, "}"
CLOSE #fp 'Close file

PRINT "File "+BAS_FILENAME+" Saved."
PRINT "Press any key to exit..."
SLEEP
Теперь мы можем заменить файл из title.bi в первом уроки книги "Давайте сделаем рогалик", на полученный в результате выполнения данной программы и скомпилировать урок. Полученный результат можно наблюдать в заголовке статьи.

2. Что можно улучшить

Как можно убедиться, наша программа выполняет поставленную задачу. Однако, если нам подадобиться сконвертировать картинку, которая сохранена в файле под именем, отличным от title.bmp, то нам необходимо будет перекомпилировать программу, что несколько неудобно. Исправим это, получением имени входного файла из командной строки.

2.1. Имена входного и выходного файлов

в FreeBasic-е есть зашитые в компилятор макросы __fb_argc__ и __fb_argv__, первый содердит количество параметров командной строки, второй - указатель на массив указателей строк, заканчивающихся 0. Используя их мы можем получать доступ к параметрам командной строки.
DIM AS STRING bmpFile, basFile 'Строковые переменные для имен файлов

DIM argc AS INTEGER = __fb_argc__ 'Количество параметров
DIM argv AS ZSTRING PTR PTR = __fb_argv__ 'Указатель на массив параметров

'Get bmp and output bas file name from command promt
SELECT CASE argc
    CASE 2                          'если не задано имя выходного файла
        bmpFile = *argv[1]          'в argv[1] содержиться указатель на строку. используем * для доступа к содержимому
        basFile = BAS_FILENAME      'имя выходного задается значением по умолчанию
    CASE 3                          'оба параметра заданы 
        bmpFile = *argv[1]          'имя входного файла
        basFile = *argv[2]          'имя выходного файла
    CASE ELSE                       'не задано имя входного файла - выведем помощь по использованию программы
        PRINT "BMP Image to FreeBasic array Converter. Version 2."
        PRINT "Usage: bmp_to_bas.v2 imput_bmp_filename [ output_bas_filename ]"
        END
END SELECT
Теперь у нас в переменных bmpFile и basFile содержаться имена входного и выходного файлов, которые можно задавать в командной строке при вызове программы, без необходимости ее перекомпиляции. Однако, нам все еще нужно будет перекомпилировать программу, если размер исходного изображения отличается от заданных нами по умолчанию 80x60 пикселей, мы могли бы также считыватьэти значения из командной строки, но можно их получать прямо из самого изображения.

2.2. Размер изображения

Так как формат bmp файлов известен, а в нем по смещениям 19 и 23, от начала файла, храняться, соответственно, высота и ширина изображения, то мы можем их прочитать прямо из самого файла картинки.
'Переменные для хранения высоты и ширина исходного изображения
DIM AS INTEGER bmpwidth, bmpheight
'Откроем файл на чтение и бинарном режиме
fp = FREEFILE()
IF OPEN( bmpFile FOR BINARY ACCESS READ AS #fp ) <> 0 THEN
    PRINT "Error open input BMP file: "; bmpFile      'Сообщение об ошибке и выход из программы, если невозможно открыть файл
    END
ELSE
    'Get BMP dimensions
    GET #fp, 19, bmpwidth        'читаем 4 байта
    GET #fp, 23, bmpheight       'читаем 4 байта
ENDIF
CLOSE #fp 'закроем файл
Теперь у нас есть высота и ширина изображения. Так как изначально мы не знаем их, то нам необходимо изменить принцип объявления массива цветов. а именно, в начале программы объявим массив нулевой длинны:
DIM title() AS UINTEGER
после того, как получим значение высоты и ширины изображения, изменим размерность массива:
REDIM title(0 To bmpwidth * bmpheight) AS UINTEGER
Теперь нашу программу можно вызывать командами:
bmp_to_bas.v2 title.bmp
или
bmp_to_bas.v2 title.bmp title.bi
при этом правильно будут обрабатываться изображения различного разрешения. полный код программы:
/'****************************************************************************
*
* Name: bmp_to_bas.bas
*
* Description: Demo for chapter 1 of Richard D. Clark wikibook 
*              "Let's Build a Roguelike"
*
* Synopsys:  BMP image to bas source converter. Version 2
*
* Copyright 2013, Kanstantsin A. Ashuika (aka Uvadzucumi, Fantik, etc...)
*
*                          The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies. 
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/
#Include "fbgfx.bi"

#define BAS_FILENAME "title.bi" 'Output bas file name

'BMP Image Width and Height
DIM AS INTEGER bmpwidth, bmpheight

DIM buffer AS ANY PTR 'Pointer for load bmp image
DIM AS INTEGER x,y
DIM title() AS UINTEGER
DIM AS INTEGER fp

DIM AS STRING bmpFile, basFile

DIM argc AS INTEGER = __fb_argc__
DIM argv AS ZSTRING PTR PTR = __fb_argv__

'Get bmp and output bas file name from command promt
SELECT CASE argc
    CASE 2
        bmpFile = *argv[1]          ' argv[1] - contained pointer to first parameter, * - for return string
        basFile = BAS_FILENAME
    CASE 3
        bmpFile = *argv[1]
        basFile = *argv[2]
    CASE ELSE
        PRINT "BMP Image to FreeBasic array Converter. Version 2."
        PRINT "Usage: bmp_to_bas.v2 imput_bmp_filename [ output_bas_filename ]"
        END
END SELECT
 
SCREENRES 640, 480, 16 'Create graphics window

'Get bitmap image width and height
fp = FREEFILE()
IF OPEN( bmpFile FOR BINARY ACCESS READ AS #fp ) <> 0 THEN
    PRINT "Error open input BMP file: "; bmpFile
    END
ELSE
    'Get BMP dimensions
    GET #fp, 19, bmpwidth
    GET #fp, 23, bmpheight
ENDIF
CLOSE #fp

'Set colors array size
REDIM title(0 To bmpwidth * bmpheight) AS UINTEGER

buffer = IMAGECREATE(bmpwidth, bmpheight) 'Allocate memory for image buffer

BLOAD bmpFile, buffer 'Load image

'Block screen and draw image
SCREENLOCK
PUT (0,0), buffer
SCREENUNLOCK

'read pixel colors to array
FOR y=0 to bmpheight-1
  FOR x=0 to bmpwidth-1
    title(x+y*bmpwidth)=Point(x,y)
  NEXT x
NEXT y

PRINT "Press any key to continue..."
SLEEP

'free image buffer
IMAGEDESTROY buffer

SCREEN 0 'Text mode

fp = FreeFile()  'Get free file handle
OPEN basFile FOR OUTPUT AS #fp 'Open file for write

'Write first strings
PRINT #fp, "'Useage: (uinteger) pixel = title(x + y * titlew)"
PRINT #fp, "#Define titlew "; bmpwidth
PRINT #fp, "#Define titleh "; bmpheight
PRINT #fp, "Dim Shared title(";(bmpwidth * bmpheight);") As UInteger = { _"

FOR y=0 to bmpheight-1
  FOR x=0 to bmpwidth-1
    PRINT #fp, title(x+y*bmpwidth);
    IF y<(bmpheight-1) OR x<(bmpwidth-1) THEN PRINT #fp,", ";
  NEXT x
  PRINT #fp, " _"
NEXT y
' Write last string
PRINT #fp, "}"
CLOSE #fp 'Close file

PRINT "File "+basFile+" Saved."
PRINT "Press any key to exit..."
SLEEP

3. Мысли на тему

На самом деле, как можно увидеть, мы делаем кучу ненужных промежуточныx действи. Т.е. мы грузим bmp картинку (которая из себя представляет двумерный массив цветов) в память, рисуем ее на экране (копируем массив цветов из кортинки в память видебуффера), читаем цвета пикселей в массив (массив цветов из видеобуффера в массив в оперативной памяти), сохраняем этот массив на диск в виде free basic кода. Кучу всех этих повторных копирований можно избежать, просто загрузив bmp файл в память и, читая цвета из оперативной памяти, отрисовывавать титульный экран.

4. Полезные ссылки

2 комментария:

  1. Большое спасибо за программу

    ОтветитьУдалить
  2. В 7-й винде код неправильно определял размер картинки, в результате программа падала с кастрюльным звоном, заменил на такой

    #include "windows.bi"
    ....
    dim as BITMAPINFOHEADER ptr pbmi = allocate(sizeof(BITMAPINFOHEADER))
    IF OPEN( bmpFile FOR BINARY ACCESS READ AS #fp ) <> 0 THEN
    PRINT "Error open input BMP file: "; bmpFile
    END
    ELSE
    get #fp, sizeof(BITMAPFILEHEADER)+1, *pbmi
    bmpwidth = pbmi->biWidth
    bmpheight = pbmi->biHeight
    deallocate pbmi
    ENDIF
    ....
    Идея взята отсюда http://www.freebasic.net/forum/viewtopic.php?f=3&t=21201
    Картинку сохранял в Paint как 24х разрядный bmp.

    ОтветитьУдалить