442 lines
14 KiB
C
442 lines
14 KiB
C
/*
|
|
* libcaca Colour ASCII-Art library
|
|
* Copyright © 2002—2021 Sam Hocevar <sam@hocevar.net>
|
|
* All Rights Reserved
|
|
*
|
|
* This library is free software. It comes without any warranty, to
|
|
* the extent permitted by applicable law. You can redistribute it
|
|
* and/or modify it under the terms of the Do What the Fuck You Want
|
|
* to Public License, Version 2, as published by Sam Hocevar. See
|
|
* http://www.wtfpl.net/ for more details.
|
|
*/
|
|
|
|
/*
|
|
* This file contains various import functions.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if !defined __KERNEL__
|
|
# include <stdlib.h>
|
|
# include <string.h>
|
|
# include <stdio.h>
|
|
#endif
|
|
|
|
#include "caca.h"
|
|
#include "caca_internals.h"
|
|
#include "codec.h"
|
|
|
|
static inline uint32_t sscanu32(void const *s)
|
|
{
|
|
uint32_t x;
|
|
memcpy(&x, s, 4);
|
|
return hton32(x);
|
|
}
|
|
|
|
static inline uint16_t sscanu16(void const *s)
|
|
{
|
|
uint16_t x;
|
|
memcpy(&x, s, 2);
|
|
return hton16(x);
|
|
}
|
|
|
|
static ssize_t import_caca(caca_canvas_t *, void const *, size_t);
|
|
|
|
/** \brief Import a memory buffer into a canvas
|
|
*
|
|
* Import a memory buffer into the given libcaca canvas's current
|
|
* frame. The current frame is resized accordingly and its contents are
|
|
* replaced with the imported data.
|
|
*
|
|
* Valid values for \c format are:
|
|
* - \c "": attempt to autodetect the file format.
|
|
* - \c "caca": import native libcaca files.
|
|
* - \c "text": import ASCII text files.
|
|
* - \c "ansi": import ANSI files.
|
|
* - \c "utf8": import UTF-8 files with ANSI colour codes.
|
|
* - \c "bin": import BIN files.
|
|
*
|
|
* The number of bytes read is returned. If the file format is valid, but
|
|
* not enough data was available, 0 is returned.
|
|
*
|
|
* If an error occurs, -1 is returned and \b errno is set accordingly:
|
|
* - \c ENOMEM Not enough memory to allocate canvas.
|
|
* - \c EOVERFLOW Importing data caused a value overflow.
|
|
* - \c EINVAL Invalid format requested.
|
|
*
|
|
* \param cv A libcaca canvas in which to import the file.
|
|
* \param data A memory area containing the data to be loaded into the canvas.
|
|
* \param len The size in bytes of the memory area.
|
|
* \param format A string describing the input format.
|
|
* \return The number of bytes read, or 0 if there was not enough data,
|
|
* or -1 if an error occurred.
|
|
*/
|
|
ssize_t caca_import_canvas_from_memory(caca_canvas_t *cv, void const *data,
|
|
size_t len, char const *format)
|
|
{
|
|
if(!strcasecmp("caca", format))
|
|
return import_caca(cv, data, len);
|
|
if(!strcasecmp("utf8", format))
|
|
return _import_ansi(cv, data, len, 1);
|
|
if(!strcasecmp("text", format))
|
|
return _import_text(cv, data, len);
|
|
if(!strcasecmp("ansi", format))
|
|
return _import_ansi(cv, data, len, 0);
|
|
if(!strcasecmp("bin", format))
|
|
return _import_bin(cv, data, len);
|
|
|
|
/* Autodetection */
|
|
if(!strcasecmp("", format))
|
|
{
|
|
unsigned char const *str = data;
|
|
unsigned int i, j, k;
|
|
|
|
/* If 4 first bytes are 0xcaca + 'CV' */
|
|
if(len >= 4 && str[0] == 0xca &&
|
|
str[1] == 0xca && str[2] == 'C' && str[3] == 'V')
|
|
return import_caca(cv, data, len);
|
|
|
|
/* If we find ESC[ argv, we guess it's an ANSI file */
|
|
for(i = 0; i + 1 < len; i++)
|
|
if((str[i] == '\033') && (str[i + 1] == '['))
|
|
return _import_ansi(cv, data, len, 0);
|
|
|
|
/* If we find a lot of spaces at even locations,
|
|
* we guess it's a BIN file. */
|
|
for (i = j = k = 0; i < len; i += 2)
|
|
{
|
|
j += (str[i] == ' ');
|
|
k += (str[i + 1] == ' ');
|
|
}
|
|
|
|
if (j > 10 && j > len / 40 && k < 10)
|
|
return _import_bin(cv, data, len);
|
|
|
|
/* Otherwise, import it as text */
|
|
return _import_text(cv, data, len);
|
|
}
|
|
|
|
seterrno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/** \brief Import a file into a canvas
|
|
*
|
|
* Import a file into the given libcaca canvas's current frame. The
|
|
* current frame is resized accordingly and its contents are replaced
|
|
* with the imported data.
|
|
*
|
|
* Valid values for \c format are:
|
|
* - \c "": attempt to autodetect the file format.
|
|
* - \c "caca": import native libcaca files.
|
|
* - \c "text": import ASCII text files.
|
|
* - \c "ansi": import ANSI files.
|
|
* - \c "utf8": import UTF-8 files with ANSI colour codes.
|
|
* - \c "bin": import BIN files.
|
|
*
|
|
* The number of bytes read is returned. If the file format is valid, but
|
|
* not enough data was available, 0 is returned.
|
|
*
|
|
* If an error occurs, -1 is returned and \b errno is set accordingly:
|
|
* - \c ENOSYS File access is not implemented on this system.
|
|
* - \c ENOMEM Not enough memory to allocate canvas.
|
|
* - \c EINVAL Invalid format requested.
|
|
* caca_import_file() may also fail and set \b errno for any of the
|
|
* errors specified for the routine fopen().
|
|
*
|
|
* \param cv A libcaca canvas in which to import the file.
|
|
* \param filename The name of the file to load.
|
|
* \param format A string describing the input format.
|
|
* \return The number of bytes read, or 0 if there was not enough data,
|
|
* or -1 if an error occurred.
|
|
*/
|
|
ssize_t caca_import_canvas_from_file(caca_canvas_t *cv, char const *filename,
|
|
char const *format)
|
|
{
|
|
#if defined __KERNEL__
|
|
seterrno(ENOSYS);
|
|
return -1;
|
|
#else
|
|
caca_file_t *f;
|
|
char *data = NULL;
|
|
ssize_t ret, size = 0;
|
|
|
|
f = caca_file_open(filename, "rb");
|
|
if(!f)
|
|
return -1; /* fopen already set errno */
|
|
|
|
while(!caca_file_eof(f))
|
|
{
|
|
data = realloc(data, size + 1024);
|
|
if(!data)
|
|
{
|
|
caca_file_close(f);
|
|
seterrno(ENOMEM);
|
|
return -1;
|
|
}
|
|
|
|
ret = (ssize_t)caca_file_read(f, data + size, 1024);
|
|
if(ret >= 0)
|
|
size += ret;
|
|
}
|
|
caca_file_close(f);
|
|
|
|
ret = caca_import_canvas_from_memory(cv, data, size, format);
|
|
free(data);
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
/** \brief Import a memory buffer into a canvas area
|
|
*
|
|
* Import a memory buffer into the given libcaca canvas's current
|
|
* frame, at the specified position. For more information, see
|
|
* caca_import_canvas_from_memory().
|
|
*
|
|
* If an error occurs, -1 is returned and \b errno is set accordingly:
|
|
* - \c EINVAL Unsupported format requested or invalid coordinates.
|
|
* - \c ENOMEM Not enough memory to allocate canvas.
|
|
*
|
|
* \param cv A libcaca canvas in which to import the file.
|
|
* \param x The leftmost coordinate of the area to import to.
|
|
* \param y The topmost coordinate of the area to import to.
|
|
* \param data A memory area containing the data to be loaded into the canvas.
|
|
* \param len The size in bytes of the memory area.
|
|
* \param format A string describing the input format.
|
|
* \return The number of bytes read, or 0 if there was not enough data,
|
|
* or -1 if an error occurred.
|
|
*/
|
|
ssize_t caca_import_area_from_memory(caca_canvas_t *cv, int x, int y,
|
|
void const *data, size_t len,
|
|
char const *format)
|
|
{
|
|
caca_canvas_t *tmp;
|
|
ssize_t ret;
|
|
|
|
tmp = caca_create_canvas(0, 0);
|
|
ret = caca_import_canvas_from_memory(tmp, data, len, format);
|
|
|
|
if(ret > 0)
|
|
caca_blit(cv, x, y, tmp, NULL);
|
|
|
|
caca_free_canvas(tmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Import a file into a canvas area
|
|
*
|
|
* Import a file into the given libcaca canvas's current frame, at the
|
|
* specified position. For more information, see
|
|
* caca_import_canvas_from_file().
|
|
*
|
|
* If an error occurs, -1 is returned and \b errno is set accordingly:
|
|
* - \c ENOSYS File access is not implemented on this system.
|
|
* - \c ENOMEM Not enough memory to allocate canvas.
|
|
* - \c EINVAL Unsupported format requested or invalid coordinates.
|
|
* caca_import_file() may also fail and set \b errno for any of the
|
|
* errors specified for the routine fopen().
|
|
*
|
|
* \param cv A libcaca canvas in which to import the file.
|
|
* \param x The leftmost coordinate of the area to import to.
|
|
* \param y The topmost coordinate of the area to import to.
|
|
* \param filename The name of the file to load.
|
|
* \param format A string describing the input format.
|
|
* \return The number of bytes read, or 0 if there was not enough data,
|
|
* or -1 if an error occurred.
|
|
*/
|
|
ssize_t caca_import_area_from_file(caca_canvas_t *cv, int x, int y,
|
|
char const *filename, char const *format)
|
|
{
|
|
caca_canvas_t *tmp;
|
|
ssize_t ret;
|
|
|
|
tmp = caca_create_canvas(0, 0);
|
|
ret = caca_import_canvas_from_file(tmp, filename, format);
|
|
|
|
if(ret > 0)
|
|
caca_blit(cv, x, y, tmp, NULL);
|
|
|
|
caca_free_canvas(tmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Get available import formats
|
|
*
|
|
* Return a list of available import formats. The list is a NULL-terminated
|
|
* array of strings, interleaving a string containing the internal value for
|
|
* the import format, to be used with caca_import_canvas(), and a string
|
|
* containing the natural language description for that import format.
|
|
*
|
|
* This function never fails.
|
|
*
|
|
* \return An array of strings.
|
|
*/
|
|
char const * const * caca_get_import_list(void)
|
|
{
|
|
static char const * const list[] =
|
|
{
|
|
"", "autodetect",
|
|
"caca", "native libcaca format",
|
|
"text", "plain text",
|
|
"ansi", "ANSI coloured text",
|
|
"utf8", "UTF-8 files with ANSI colour codes",
|
|
"bin", "BIN binary ANSI art",
|
|
NULL, NULL
|
|
};
|
|
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* XXX: the following functions are local.
|
|
*/
|
|
|
|
static ssize_t import_caca(caca_canvas_t *cv, void const *data, size_t size)
|
|
{
|
|
uint8_t const *buf = (uint8_t const *)data;
|
|
size_t control_size, data_size, expected_size;
|
|
unsigned int frames, f, n, offset;
|
|
uint16_t version, flags;
|
|
int32_t xmin = 0, ymin = 0, xmax = 0, ymax = 0;
|
|
|
|
if(size < 20)
|
|
return 0;
|
|
|
|
if(buf[0] != 0xca || buf[1] != 0xca || buf[2] != 'C' || buf[3] != 'V')
|
|
{
|
|
debug("caca import error: expected \\xca\\xcaCV header");
|
|
goto invalid_caca;
|
|
}
|
|
|
|
control_size = sscanu32(buf + 4);
|
|
data_size = sscanu32(buf + 8);
|
|
version = sscanu16(buf + 12);
|
|
frames = sscanu32(buf + 14);
|
|
flags = sscanu16(buf + 18);
|
|
|
|
if(size < 4 + control_size + data_size)
|
|
return 0;
|
|
|
|
if(control_size < 16 + frames * 32)
|
|
{
|
|
debug("caca import error: control size %u < expected %u",
|
|
(unsigned int)control_size, 16 + frames * 32);
|
|
goto invalid_caca;
|
|
}
|
|
|
|
for(expected_size = 0, f = 0; f < frames; f++)
|
|
{
|
|
unsigned int width, height, duration;
|
|
uint32_t attr;
|
|
int x, y, handlex, handley;
|
|
|
|
width = sscanu32(buf + 4 + 16 + f * 32);
|
|
height = sscanu32(buf + 4 + 16 + f * 32 + 4);
|
|
duration = sscanu32(buf + 4 + 16 + f * 32 + 8);
|
|
attr = sscanu32(buf + 4 + 16 + f * 32 + 12);
|
|
x = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 16);
|
|
y = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 20);
|
|
handlex = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 24);
|
|
handley = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 28);
|
|
expected_size += width * height * 8;
|
|
if(-handlex < xmin)
|
|
xmin = -handlex;
|
|
if(-handley < ymin)
|
|
ymin = -handley;
|
|
if((((int32_t) width) - handlex) > xmax)
|
|
xmax = ((int32_t) width) - handlex;
|
|
if((((int32_t) height) - handley) > ymax)
|
|
ymax = ((int32_t) height) - handley;
|
|
}
|
|
|
|
if(expected_size != data_size)
|
|
{
|
|
debug("caca import error: data size %u < expected %u",
|
|
(unsigned int)data_size, (unsigned int)expected_size);
|
|
goto invalid_caca;
|
|
}
|
|
|
|
caca_set_canvas_size(cv, 0, 0);
|
|
caca_set_canvas_size(cv, xmax - xmin, ymax - ymin);
|
|
|
|
for (f = caca_get_frame_count(cv); f--; )
|
|
{
|
|
caca_free_frame(cv, f);
|
|
}
|
|
|
|
for (offset = 0, f = 0; f < frames; f ++)
|
|
{
|
|
unsigned int width, height;
|
|
|
|
width = sscanu32(buf + 4 + 16 + f * 32);
|
|
height = sscanu32(buf + 4 + 16 + f * 32 + 4);
|
|
caca_create_frame(cv, f);
|
|
caca_set_frame(cv, f);
|
|
|
|
cv->curattr = sscanu32(buf + 4 + 16 + f * 32 + 12);
|
|
cv->frames[f].x = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 16);
|
|
cv->frames[f].y = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 20);
|
|
cv->frames[f].handlex = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 24);
|
|
cv->frames[f].handley = (int32_t)sscanu32(buf + 4 + 16 + f * 32 + 28);
|
|
|
|
/* FIXME: check for return value */
|
|
|
|
for(n = width * height; n--; )
|
|
{
|
|
int x = (n % width) - cv->frames[f].handlex - xmin;
|
|
int y = (n / width) - cv->frames[f].handley - ymin;
|
|
|
|
caca_put_char(cv, x, y, sscanu32(buf + 4 + control_size
|
|
+ offset + 8 * n));
|
|
caca_put_attr(cv, x, y, sscanu32(buf + 4 + control_size
|
|
+ offset + 8 * n + 4));
|
|
}
|
|
offset += width * height * 8;
|
|
|
|
cv->frames[f].x -= cv->frames[f].handlex;
|
|
cv->frames[f].y -= cv->frames[f].handley;
|
|
cv->frames[f].handlex = -xmin;
|
|
cv->frames[f].handley = -ymin;
|
|
}
|
|
|
|
caca_set_frame(cv, 0);
|
|
|
|
return (ssize_t)(4 + control_size + data_size);
|
|
|
|
invalid_caca:
|
|
seterrno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
ssize_t _import_bin(caca_canvas_t *cv, void const *data, size_t len)
|
|
{
|
|
uint8_t const *buf = (uint8_t const *)data;
|
|
size_t i;
|
|
int x = 0, y = 0;
|
|
|
|
caca_set_canvas_size(cv, 0, 0);
|
|
caca_set_canvas_size(cv, 160, len / 160);
|
|
|
|
/* Only read an even number of bytes */
|
|
len &= ~(size_t)1;
|
|
|
|
for (i = 0; i < len; i += 2)
|
|
{
|
|
caca_set_color_ansi(cv, buf[i + 1] & 0xf, buf[i + 1] >> 4);
|
|
caca_put_char(cv, x, y, caca_cp437_to_utf32(buf[i]));
|
|
|
|
++x;
|
|
if (x >= 160)
|
|
{
|
|
++y;
|
|
x = 0;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|