view src/pic2img.cc @ 103:4ef9dfbd82b6

Cosmetics.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 06 Oct 2014 04:02:47 +0300
parents 2f1ecc1c5f72
children 20aa5a515896
line wrap: on
line source

/*
 *        pic2img.cc
 *        Loading Doom-format pictures from a wad file.
 *        See the Unofficial Doom Specs, section [5-1].
 *        AYM 1998-??-??
 */


/*
This file is part of Yadex.

Yadex incorporates code from DEU 5.21 that was put in the public domain in
1994 by Raphaël Quinet and Brendon Wyber.

The rest of Yadex is Copyright © 1997-2003 André Majorel and others.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.
*/


#include "yadex.h"
#include "gcolour2.h"                /* colour0 */
#include "pic2img.h"
#include "wadfile.h"
#include "wads.h"


typedef enum
{ _MT_BADOFS, _MT_TOOLONG, _MT_TOOMANY } _msg_type_t;


static int add_msg(char type, int arg);
static void do_add_msg(char type, int arg);
static void flush_msg(const char *picname);


/*
 *        LoadPicture - read a picture from a wad file into an Img object
 *
 *        If img->is_null() is false, LoadPicture() does not allocate the
 *        buffer itself. The buffer and the picture don't have to have the
 *        same dimensions. Thanks to this, it can also be used to compose
 *        textures : you allocate a single buffer for the whole texture
 *        and then you call LoadPicture() on it once for each patch.
 *        LoadPicture() takes care of all the necessary clipping.
 *
 *        If img->is_null() is true, LoadPicture() sets the size of img
 *        to match that of the picture. This is useful in display_pic().
 *
 *        Return 0 on success, non-zero on failure.
 *
 *        If pic_x_offset == INT_MIN, the picture is centred horizontally.
 *        If pic_y_offset == INT_MIN, the picture is centred vertically.
 */

int LoadPicture(Img & img,        // Game image to load picture into
                const char *picname,        // Picture lump name
                const Lump_loc & picloc,        // Picture lump location
                int pic_x_offset,        // Coordinates of top left corner of picture
                int pic_y_offset,        // relative to top left corner of buffer
                int *pic_width,        // To return the size of the picture
                int *pic_height)        // (can be NULL)
{
    MDirPtr dir;
    i16 pic_width_;
    i16 pic_height_;
    i16 pic_intrinsic_x_ofs;
    i16 pic_intrinsic_y_ofs;
    u8 *ColumnData;
    u8 *Column;
    i32 *NeededOffsets;
    i32 CurrentOffset;
    int ColumnInMemory;
    long ActualBufLen;
    int pic_x;
    int pic_x0;
    int pic_x1;
    int pic_y0;
    int pic_y1;
    u8 *buf;                        /* This variable is set to point to the element of
                                   the image buffer where the top of the current column
                                   should be pasted. It can be off the image buffer! */

    // Locate the lump where the picture is
    if (picloc.wad != 0)
    {
        MasterDirectory dirbuf;
        dirbuf.wadfile = picloc.wad;
        dirbuf.dir.start = picloc.ofs;
        dirbuf.dir.size = picloc.len;
        dir = &dirbuf;
    }
    else
    {
        dir = (MDirPtr) FindMasterDir(cfg.MasterDir, picname);
        if (dir == NULL)
        {
            warn("picture %.*s does not exist.\n", WAD_PIC_NAME, picname);
            return 1;
        }
    }

    // Read the picture header
    dir->wadfile->seek(dir->dir.start);
    if (dir->wadfile->error())
    {
        warn("picture %.*s: can't seek to header, giving up\n",
             WAD_PIC_NAME, picname);
        return 1;
    }
    bool dummy_bytes = dir->wadfile->pic_format() == YGPF_NORMAL;
    bool long_header = dir->wadfile->pic_format() != YGPF_ALPHA;
    bool long_offsets = dir->wadfile->pic_format() == YGPF_NORMAL;
    if (long_header)
    {
        dir->wadfile->read_i16(&pic_width_);
        dir->wadfile->read_i16(&pic_height_);
        dir->wadfile->read_i16(&pic_intrinsic_x_ofs);        // Read but ignored
        dir->wadfile->read_i16(&pic_intrinsic_y_ofs);        // Read but ignored
        if (dir->wadfile->error())
        {
            warn("picture %.*s: read error in header, giving up\n",
                 WAD_PIC_NAME, picname);
            return 1;
        }
    }
    else
    {
        pic_width_ = dir->wadfile->read_u8();
        pic_height_ = dir->wadfile->read_u8();
        pic_intrinsic_x_ofs = dir->wadfile->read_u8();        // Read but ignored
        pic_intrinsic_y_ofs = dir->wadfile->read_u8();        // Read but ignored
        if (dir->wadfile->error())
        {
            warn("picture %.*s: read error in header, giving up\n",
                 WAD_PIC_NAME, picname);
            return 1;
        }
    }

    // If no buffer given by caller, allocate one.
    if (img.is_null())
    {
        // Sanity checks
        if (pic_width_ < 1 || pic_height_ < 1)
        {
            warn("picture %.*s: delirious dimensions %dx%d, giving up\n",
                 WAD_PIC_NAME, picname, (int) pic_width_, (int) pic_height_);
        }
        const int pic_width_max = 4096;
        if (pic_width_ > pic_width_max)
        {
            warn("picture %.*s: too wide (%d), clipping to %d\n",
                 WAD_PIC_NAME, picname, (int) pic_width_, pic_width_max);
            pic_width_ = pic_width_max;
        }
        const int pic_height_max = 4096;
        if (pic_height_ > pic_height_max)
        {
            warn("picture %.*s: too high (%d), clipping to %d\n",
                 WAD_PIC_NAME, picname, (int) pic_height_, pic_height_max);
            pic_height_ = pic_height_max;
        }
        img.resize(pic_width_, pic_height_);
    }
    int img_width = img.width();

    // Centre the picture.
    if (pic_x_offset == INT_MIN)
        pic_x_offset = (img_width - pic_width_) / 2;
    if (pic_y_offset == INT_MIN)
        pic_y_offset = (img.height() - pic_height_) / 2;

/* AYM 19971202: 17 kB is large enough for 128x128 patches. */
#define TEX_COLUMNBUFFERSIZE ((long) 17 * 1024)
/* Maximum number of bytes per column. The worst case is a
   509-high column, with every second pixel transparent. That
   makes 255 posts of 1 pixel, and a final FFh. The total is
   (255 x 5 + 1) = 1276 bytes per column. */
#define TEX_COLUMNSIZE  1300

    ColumnData = (u8 *) GetMemory(TEX_COLUMNBUFFERSIZE);
/* FIXME DOS and pic_width_ > 16000 */
    NeededOffsets = (i32 *) GetMemory((long) pic_width_ * sizeof(i32));

    if (long_offsets)
        dir->wadfile->read_i32(NeededOffsets, pic_width_);
    else
        for (int n = 0; n < pic_width_; n++)
        {
            i16 ofs;
            dir->wadfile->read_i16(&ofs);
            NeededOffsets[n] = ofs;
        }
    if (dir->wadfile->error())
    {
        warn("picture %.*s: read error in offset table, giving up\n",
             WAD_PIC_NAME, picname);
        FreeMemory(ColumnData);
        FreeMemory(NeededOffsets);
        return 1;
    }

    // Read first column data, and subsequent column data
    if (long_offsets && NeededOffsets[0] != 8 + (long) pic_width_ * 4
        || !long_offsets && NeededOffsets[0] != 4 + (long) pic_width_ * 2)
    {
        dir->wadfile->seek(dir->dir.start + NeededOffsets[0]);
        if (dir->wadfile->error())
        {
            warn("picture %.*s: can't seek to header, giving up\n",
                 WAD_PIC_NAME, picname);
            FreeMemory(ColumnData);
            FreeMemory(NeededOffsets);
            return 1;
        }
    }

    ActualBufLen = dir->wadfile->read_vbytes(ColumnData, TEX_COLUMNBUFFERSIZE);
    // FIXME should catch I/O errors

    // Clip the picture horizontally and vertically
    pic_x0 = -pic_x_offset;
    if (pic_x0 < 0)
        pic_x0 = 0;

    pic_x1 = img_width - pic_x_offset - 1;
    if (pic_x1 >= pic_width_)
        pic_x1 = pic_width_ - 1;

    pic_y0 = -pic_y_offset;
    if (pic_y0 < 0)
        pic_y0 = 0;

    pic_y1 = img.height() - pic_y_offset - 1;
    if (pic_y1 >= pic_height_)
        pic_y1 = pic_height_ - 1;

    // For each (non clipped) column of the picture...
    for (pic_x = pic_x0,
         buf = img.wbuf() + al_amax(pic_x_offset, 0) + img_width * pic_y_offset;
         pic_x <= pic_x1; pic_x++, buf++)
    {
        u8 *filedata;

        CurrentOffset = NeededOffsets[pic_x];
        ColumnInMemory = CurrentOffset >= NeededOffsets[0]
            && CurrentOffset + TEX_COLUMNSIZE <=
            NeededOffsets[0] + ActualBufLen;

        if (ColumnInMemory)
            Column = ColumnData + CurrentOffset - NeededOffsets[0];
        else
        {
            Column = (u8 *) GetMemory(TEX_COLUMNSIZE);
            dir->wadfile->seek(dir->dir.start + CurrentOffset);
            if (dir->wadfile->error())
            {
                int too_many = add_msg(_MT_BADOFS, (short) pic_x);
                FreeMemory(Column);
                if (too_many)        // This picture has too many errors. Give up.
                    goto pic_end;
                continue;        // Give up on this column
            }
            dir->wadfile->read_vbytes(Column, TEX_COLUMNSIZE);
            // FIXME should catch I/O errors
        }
        filedata = Column;

        // We now have the needed column data, one way or another, so write it

        // For each post of the column...
        {
            register u8 *post;
            for (post = filedata; *post != 0xff;)
            {
                int post_y_offset;        // Y-offset of top of post to origin of buffer
                int post_height;        // Height of post
                int post_pic_y0;        // Start and end of non-clipped part of post,
                int post_pic_y1;        // relative to top of picture
                int post_y0;        // Start and end of non-clipped part of post,
                int post_y1;        // relative to top of post

                if (post - filedata > TEX_COLUMNSIZE)
                {
                    int too_many = add_msg(_MT_TOOLONG, (short) pic_x);
                    if (too_many)        // This picture has too many errors. Give up.
                    {
                        if (!ColumnInMemory)
                            FreeMemory(Column);
                        goto pic_end;
                    }
                    break;        // Give up on this column
                }

                post_y_offset = *post++;
                post_height = *post++;
                if (dummy_bytes)
                    post++;        // Skip that dummy byte

                post_pic_y0 = post_y_offset;        // Clip the post vertically
                if (post_pic_y0 < pic_y0)
                    post_pic_y0 = pic_y0;

                post_pic_y1 = post_y_offset + post_height - 1;
                if (post_pic_y1 > pic_y1)
                    post_pic_y1 = pic_y1;

                post_y0 = post_pic_y0 - post_y_offset;
                post_y1 = post_pic_y1 - post_y_offset;


                {                // "Paste" the post onto the buffer
                    register img_pixel_t *b;
                    register const u8 *p = post + post_y0;
                    const u8 *const pmax = post + post_y1;
                    int buf_width = img_width;

                    for (b = buf + buf_width * (post_y_offset + post_y0);
                         p <= pmax; b += buf_width, p++)
                    {
#ifdef PARANOIA
                        if (b < img.buf())
                        {
                            nf_bug("Picture %.*s(%d): b < buffer",
                                   WAD_PIC_NAME, picname, (int) pic_x);
                            goto next_column;
                        }
#endif
                        *b = (*p == IMG_TRANSP) ? colour0 : *p;
                    }
                }

                post += post_height;
                if (dummy_bytes)
                    post++;        // Skip the trailing dummy byte
            }                        // Post loop
        }

#ifdef PARANOIA
      next_column:
#endif
        if (!ColumnInMemory)
            FreeMemory(Column);
    }                                // Column loop

  pic_end:
    FreeMemory(ColumnData);
    FreeMemory(NeededOffsets);
    flush_msg(picname);
#if 0
    c->flags |= HOOK_DRAWN;
#endif
    if (pic_width)
        *pic_width = pic_width_;
    if (pic_height)
        *pic_height = pic_height_;
    return 0;
}


/*
 *        List to hold pending warning messages
 */
typedef struct
{
    char type;
    short arg;
} _msg_t;
static _msg_t *_msg_list = 0;
static size_t _nmsg = 0;
const size_t _granularity = 128;
const size_t _max_msg = 20;


/*
 *        add_msg
 *        Add a warning message to the list
 *
 *        Return 0 on success, <>0 if the max number of messages
 *        has been reached.
 */
static int add_msg(char type, int arg)
{
    if (_nmsg >= _max_msg)
    {
        if (_nmsg == _max_msg)        // Test in case the caller ignores our return value
            do_add_msg(_MT_TOOMANY, arg);
        return 1;
    }
    do_add_msg(type, arg);
    return 0;
}


static void do_add_msg(char type, int arg)
{
    if ((_nmsg + 1) % _granularity == 1)        // Grow list if necessary
    {
        _msg_t *new_list = (_msg_t *) realloc(_msg_list,
                                              (_nmsg / _granularity +
                                               1) * _granularity *
                                              sizeof *_msg_list);
        if (new_list == 0)        // Not enough memory ? Ignore the new message
            return;
        _msg_list = new_list;
    }
    _msg_list[_nmsg].type = type;
    _msg_list[_nmsg].arg = arg;
    _nmsg++;
    return;
}


/*
 *        flush_msg
 *        Output all pending warning messages in an smart fashion
 */
static void flush_msg(const char *picname)
{
    if (_nmsg == 0 || _msg_list == 0)
        return;

    for (_msg_type_t t = _MT_BADOFS; t <= _MT_TOOLONG; ((int &) t)++)
    {
        size_t first_msg = AL_ASIZE_T_MAX;
        size_t last_msg = AL_ASIZE_T_MAX;
        const char *str = "unknown error";
        if (t == _MT_BADOFS)
            str = "bad file offset";
        else if (t == _MT_TOOLONG)
            str = "post too long";

        for (size_t n = 0; n < _nmsg; n++)
        {
            if (_msg_list[n].type == t)
            {
                if (first_msg == AL_ASIZE_T_MAX)
                {
                    first_msg = n;
                    last_msg = n;
                }
                else
                {
                    if (_msg_list[last_msg].arg != _msg_list[n].arg - 1)
                    {
                        warn("picture %.*s(%d",
                             WAD_PIC_NAME, picname,
                             (int) _msg_list[first_msg].arg);
                        if (last_msg != first_msg)
                            warn("-%d", (int) _msg_list[last_msg].arg);
                        warn("): %s. Corrupt wad ?\n", str);
                        first_msg = n;
                        last_msg = n;
                    }
                    else
                        last_msg = n;
                }
            }
        }
        if (first_msg != AL_ASIZE_T_MAX)
        {
            warn("picture %.*s(%d",
                 WAD_PIC_NAME, picname, (int) _msg_list[first_msg].arg);
            if (last_msg != first_msg)
                warn("-%d", (int) _msg_list[last_msg].arg);
            warn("): %s. Corrupt wad ?\n", str);
        }
    }

    if (_msg_list[_nmsg - 1].type == _MT_TOOMANY)
        warn("picture %.*s: too many errors. Giving up.\n", WAD_PIC_NAME,
             picname);
    _nmsg = 0;
    free(_msg_list);
    _msg_list = 0;
}