Tag Archive: image


Récemment, j’ai eu besoin de générer des écrans à afficher sur une gameboy advance. On pourrait se dire « oui, c’est facile, tu n’as qu’à faire chauffer imagemagick pour les conversions », mais ce génial outil ne supporte pas le mode d’affichage de la console. En effet, Nintendo a utilisé l’espace de couleur RGB15 Big endian (tant qu’à faire, c’est plus amusant).

Du coup, j’ai le petit programme suivant en C89 pour l’occasion. Il permet de convertir une image bitmap de dimensions 240×160 (résolution de la GBA) avec des couleurs dans l’espace RGB24 vers un fichier RAW (les pixels écrits en dur dans le fichier sans en-tête) encodé avec le bon espace (le format binaire est décrit dans les sources).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Screen size for a GBA */
#define GBA_WIDTH    240
#define GBA_HEIGHT    160

typedef unsigned char	uint8;
typedef unsigned short	uint16;

const int BmpHeaderSize = 54;

/* BMP file info */
typedef struct
{
    char ID[2];
    long FileSize;
    long RESERVED;
    long BitmapDataOffset;
    long BitmapHeaderSize;
    long Width;
    long Height;
    short NrOfPlanes;
    short BitsPerPixel;
    long Compression;
    long BitmapDataSize;
    long HorizontalResolution;
    long VerticalResolution;
    long Colors;
    long ImportantColors;
} BmpHeader;

typedef struct
{
    uint8 lsb;
    uint8 msb;
} TwoBytes;

/* Convert 24bit RGB to 15BPP Big endian format */
TwoBytes rgb_to_gba_be ( uint8 r, uint8 g, uint8 b)
{
    TwoBytes resp;

    /* GBA Pixel are Big endian and described as follow :
     *
     *        -------LSB-------   -------MSB-------
     * Color  G G G B   B B B B   0 R R R   R R G G
     * Order  2 1 0 4   3 2 1 0   X 4 3 2   1 0 4 3
     *
     */

    /* Initialize pixel */
    resp.msb = 0;
    resp.lsb = 0;

    /* Red component */
    resp.msb = resp.msb | ( (r>>1) & 0x7C ) ;

    /* Green component */
    resp.msb = resp.msb | ( (g>>6) & 0x03 ) ;
    resp.lsb = resp.lsb | ( (g<<5) & 0xE0 ) ;

    /* Blue component */
    resp.lsb = resp.lsb | ( (b>>3) & 0x1F ) ;

    return resp;
}

int main( int argc, char** argv)
{
    FILE *pInFile, *pOutRaw;

    BmpHeader* imgProps = NULL;

    uint8 r=0,g=0,b=0;
    TwoBytes gbapix;
    int line, col;
    long i;
    const long image_sz = GBA_HEIGHT * GBA_WIDTH * 2;

    /* Check args  */
    if( argc != 3 )
    {
        printf( "Usage: %s infile outfile\n", argv[0]);
        exit(1);
    }

    /* Open source file */
    pInFile = (FILE *) fopen( argv[1], "rb");
    if( pInFile == NULL )
    {
        printf( "Unable to open input file '%s' for reading.\n", argv[1]);
        exit(1);
    }

    /* Open target file */
    pOutRaw = (FILE *) fopen( argv[2], "wb");
    if( pOutRaw == NULL )
    {
        printf( "Unable to open output file '%s' for writing.\n", argv[2]);
        fclose(pInFile);
        exit(2);
    }

    /* Check source file */
    imgProps = (BmpHeader*)malloc(sizeof(BmpHeader));

    fread( &(imgProps->ID[0]),            1, 1, pInFile);
    fread( &(imgProps->ID[1]),             1, 1, pInFile);
    fread( &(imgProps->FileSize),             4, 1, pInFile);
    fread( &(imgProps->RESERVED),             4, 1, pInFile);
    fread( &(imgProps->BitmapDataOffset),         4, 1, pInFile);
    fread( &(imgProps->BitmapHeaderSize),         4, 1, pInFile);
    fread( &(imgProps->Width),             4, 1, pInFile);
    fread( &(imgProps->Height),             4, 1, pInFile);

    fread( &(imgProps->NrOfPlanes),         2, 1, pInFile);
    fread( &(imgProps->BitsPerPixel),         2, 1, pInFile);

    fread( &(imgProps->Compression),         4, 1, pInFile);
    fread( &(imgProps->BitmapDataSize),        4, 1, pInFile);
    fread( &(imgProps->HorizontalResolution),    4, 1, pInFile);
    fread( &(imgProps->VerticalResolution),     4, 1, pInFile);
    fread( &(imgProps->Colors),             4, 1, pInFile);
    fread( &(imgProps->ImportantColors),         4, 1, pInFile);

    if(     imgProps->ID[0] != 'B' || imgProps->ID[1] != 'M' ||
        imgProps->Width != 240 || imgProps->Height != 160 ||
        imgProps->Compression != 0 || imgProps->BitsPerPixel != 24 )
    {
        printf("*** Invalid input image! ***\n");
        printf("Signature: '%c%c' Expected: 'BM'.\n", imgProps->ID[0], imgProps->ID[1] );
        printf("Width: %ld Expected: %d\n", imgProps->Width, GBA_WIDTH);
        printf("Height: %ld Expected: %d\n", imgProps->Height, GBA_HEIGHT);
        printf("Compression: %s Expected: no\n", (imgProps->Compression != 0)?"yes":"no");
        printf("Color depth: %hd Expected: %d\n", imgProps->BitsPerPixel , 24);
        fclose(pInFile);
        fclose(pOutRaw);
        exit(3);
    }

    /* Fill the output with the appropriate size */
    fseek( pOutRaw, 0, SEEK_SET);
    for ( i=0; i<image_sz; ++i)
        fputc( 0x00, pOutRaw);
    fseek( pOutRaw, 0, SEEK_SET);

    /* Convert image and write to target */
    fseek( pInFile, BmpHeaderSize, SEEK_SET);

    for( line=0; line<GBA_HEIGHT ; ++line)
    {
        fseek( pOutRaw, (GBA_HEIGHT-1-line)*GBA_WIDTH*2, SEEK_SET);

        for( col=0; col<GBA_WIDTH ; ++col)
        {
            b=fgetc( pInFile);
            g=fgetc( pInFile);
            r=fgetc( pInFile);

            gbapix = rgb_to_gba_be ( r, g, b);
            fputc( gbapix.lsb, pOutRaw);
            fputc( gbapix.msb, pOutRaw);
        }
    }

    fclose(pInFile);
    fclose(pOutRaw);
    free(imgProps);
    return 0;
}

Le code travaille ligne à ligne (attention au format BMP qui stocke les lignes dans l’ordre inversé!), et convertit chaque pixel à la volée. Il est à noter que ce code a été conçu pour mon usage spécifique (vérification du format du fichier et de la taille). La taille prise en charge peut être rendue dynamique moyennant un peu de code supplémentaire. Si ça peut vous être utile, servez-vous et happy hacking ^.^

Publicité

Vous voulez effectuer du traitement d’images en C/C++ sans vous casser la tête? C’est possible avec DevIL, une librairie légère et assez simple à manipuler.

La librairie est portable, open source et des tutoriels sommaires sont disponibles.

Elle est compatible avec:

  • Windows
  • Linux dont:
    • Debian (et probablement Ubuntu) dans le dépot sous les noms de libdevil1c2 et libdevil-dev
    • Red Hat et tout ce qui a des RPM
    • Slackware

Pour les courageux, il est possible de la recompiler, mais des binaires sont déjà fournis.

Il est manifestement possible de s’en servir pour charger des textures avec OpenGL.

Si vous ne voulez pas vous casser la tête avec la librairie, voici des sources perso qui vous permettront de manipuler votre image comme un tableau unidimentionnel:

Voici un exemple de traitement d’image (réhaussement de LSB avec possibilité de filtrer par calque).

fichier lsbManipulator.hpp

#ifndef __LSBMANIPULATOR_HPP
#define __LSBMANIPULATOR_HPP

#include <IL/il.h>

class lsbManipulator {
private:
    //	Layer from the lsb to show.
    static ILuint showLayer;
    static bool showOneLayer;
public:
    static void setVisibleBitLayer( ILuint nLayer);
    static void lsbEnhancer( ILubyte* data, ILuint width, ILuint height);
};

#endif //__LSBMANIPULATOR_HPP

fichier lsbManipulator.cpp

#include <iostream>

#include "lsbManipulator.hpp"

// default init list
ILuint lsbManipulator::showLayer = 0;
bool lsbManipulator::showOneLayer = false;

void lsbManipulator::lsbEnhancer(ILubyte* data, ILuint width, ILuint height)
{
    ILuint picLength = width * height * 3;
    
    ILubyte oneByte;
    
    for( ILuint i=0; i<picLength; ++i)
    {
        // Swap endianness of the byte
        data[i] = ((data[i] * 0x0802LU | 0x22110LU ) | (data[i] * 0x0802LU | 0x88440LU )) * 0x10101LU >> 16;
	
        oneByte = data[i];
        // If applicable, shift to the n-layer and bitmask
        if( lsbManipulator::showOneLayer)
	    data[i] = (oneByte << lsbManipulator::showLayer) & 0x80LU;
	
    }
}

void lsbManipulator::setVisibleBitLayer(ILuint nLayer)
{
    lsbManipulator::showOneLayer = true;
    lsbManipulator::showLayer = nLayer;
}

Ici, vous trouverez tout le code qui va permettre d’ouvrir l’image, de lancer traitement, et de sauver le résultat. La fonction n’est pas encore thread safe, je vous tiendrai au courant quand ça sera le cas, (avec une classe, ça sera vite réglé 😉 ).

fichier pictureProcessor.h

#ifndef __PICTUREPROCESSOR_H
#define __PICTUREPROCESSOR_H

#include <string>
#include <IL/il.h>

/**
 * ilProcessor -- Image stream processor function
 *	 Functions of this type can process pictures as provided by the OpenIL layer.
 *
 * @author Geoffroy GRAMAIZE
 */
typedef void (*ilProcessor)( ILubyte*, ILuint, ILuint);

/**
 * processPicture() -- Process an image using an ilProcessor and OpenIL.
 * 	This is a wrapper function for easy OpenIL handling.
 *
 * @author Geoffroy GRAMAIZE
 *
 * @param inFileName	The input picture file path.
 * @param outFileName	The output picture file path.
 * @param ilProcessingAlg	ilProcessor used to process the input picture.
 */
void processPicture( const std::string& inFileName, const std::string& outFileName, ilProcessor ilProcessingAlg);

#endif //__PICTUREPROCESSOR_H

fichier pictureProcessor.cpp

#include <iostream>

#include <IL/il.h>
#include <IL/ilu.h>
#include <stdlib.h>
#include <string.h>

#include "pictureProcessor.h"

void processPicture( const std::string& inFileName,const std::string& outFileName, ilProcessor ilProcessingAlg)
{
    ilInit();
    ILuint imageName;
    ilGenImages(1, &imageName);
    ilBindImage(imageName);

    if( !ilLoadImage(inFileName.c_str()) )
    {
	std::cerr << "Couldn't load '" << inFileName << "' , aborting..." << std::endl;
    }

    ILuint width = ilGetInteger(IL_IMAGE_WIDTH);
    ILuint height = ilGetInteger(IL_IMAGE_HEIGHT);

    std::clog << "Loaded '" << inFileName << "' (wid: " << width << "px, hei: " << height << "px)." << std::endl;

    ILuint tailleUniDim = width*height*3;

    // grab the original picture, and create a temporary memory array
    ILubyte *dataImg = ilGetData(),
	    *dataTemp = new ILubyte[ tailleUniDim ];

    // copy the picture into the work array
    memcpy (dataTemp, dataImg, tailleUniDim);

    // call the picture processor
    ilProcessingAlg( dataTemp, width, height);

    // restore the work array
    memcpy (dataImg, dataTemp, tailleUniDim);

    // Write the picture in output file
    ilEnable(IL_FILE_OVERWRITE);

    if( ! ilSaveImage( outFileName.c_str()) )
    {
	std::cerr << "Couldn't save work in '" << inFileName << "' , aborting..." << std::endl;
    }

    ilShutDown();
}

Pour vous mettre sur la voie, voiçi un exemple d’utilisation du tout 😉

fichier argParser.hpp

#ifndef __ARGPARSER_HPP
#define __ARGPARSER_HPP

#include <iostream>

struct SAppParams {
  bool renderOneLayer; 
  unsigned int layerToRender; 
  std::string inFile; 
  std::string outFile; 
  
  static void init(SAppParams& p) {
    p.renderOneLayer=false;
    p.layerToRender=0;
    p.inFile = std::string("");
    p.outFile = std::string("");
  }
};

bool processArgs( SAppParams& params, int argc, char** argv);

#endif //__ARGPARSER_HPP

fichier argParser.cpp

#include <string.h>
#include <stdlib.h>

#include "argParser.hpp"


bool processArgs( SAppParams& params, int argc, char** argv)
{
    if( argc<2)
      return false;
    
    for( int i=1; i<argc; ++i)
    {
      
      if( strncmp( argv[i], "-l", 2) == 0 )
      {
	// Show one layer
	if( i>= argc)
	  break;
	
	params.layerToRender = atoi(argv[++i]);
	params.renderOneLayer = true;
      }
      
      else if( strncmp( argv[i], "-o", 2) == 0 )
      {
	// Set the output File
	if( i>= argc)
	  break;
	params.outFile = std::string( argv[i+1]);
      }
      else
      {
	// Set the input file
	params.inFile = std::string( argv[i]);
      }
    }
    
    return true;
}

fichier main.cpp

#include <iostream>
#include <string>

#include <IL/il.h>
#include <stdlib.h>
#include <string.h>

#include "pictureProcessor.h"
#include "lsbManipulator.hpp"
#include "argParser.hpp"

void welcomeBanner(void)
{
    std::cout << std::endl
	      << "Geoffroy Gramaize's image tickler - (c) 2011" << std::endl
	      << "--------------------------------------------" << std::endl
	      << std::endl;
}

void usage(char **argv)
{
    std::cout << "Usage: "<< argv[0] <<" args inputFile" << std::endl
	      << std::endl
	      << "Arguments\tMeaning"<< std::endl
	      << "-l <0-7> \tShow only the N-layer starting from LSB" << std::endl
	      << "-o <file>\tSpecifies in which file the result will be stored." << std::endl
	      << std::endl;
}

int main(int argc, char **argv) {
  
  SAppParams appParams;
  SAppParams::init(appParams);
  
  if( !processArgs( appParams, argc, argv) || appParams.inFile.compare("") == 0 || appParams.outFile.compare("") == 0 )
  {
    usage(argv);
    return 1;
  }
  
  welcomeBanner();
  
  if( appParams.renderOneLayer==true && appParams.layerToRender >= 0 && appParams.layerToRender <8)
  {
    
    std::clog << "Will render layer " << appParams.layerToRender << " from LSB." << std::endl;
    lsbManipulator::setVisibleBitLayer( appParams.layerToRender);
  }
  
  processPicture( appParams.inFile, appParams.outFile, &(lsbManipulator::lsbEnhancer) );
  
  return 0;
}

Bonne chance et amusez-vous bien.