2004-02-25 18:12:58 +03:00
|
|
|
/*
|
|
|
|
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
|
2005-06-23 21:24:23 +04:00
|
|
|
* Copyright 2005 Richard Wilson <info@tinct.net>
|
2007-08-08 20:16:03 +04:00
|
|
|
*
|
|
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
|
|
*
|
|
|
|
* NetSurf 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; version 2 of the License.
|
|
|
|
*
|
|
|
|
* NetSurf 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, see <http://www.gnu.org/licenses/>.
|
2004-02-25 18:12:58 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
/** \file
|
|
|
|
* Page thumbnail creation (implementation).
|
|
|
|
*
|
|
|
|
* Thumbnails are created by redirecting output to a sprite and rendering the
|
|
|
|
* page at a small scale.
|
|
|
|
*/
|
|
|
|
|
2005-09-08 00:22:33 +04:00
|
|
|
#include <assert.h>
|
2004-04-10 22:19:35 +04:00
|
|
|
#include <string.h>
|
|
|
|
#include <swis.h>
|
2005-02-20 16:19:19 +03:00
|
|
|
#include "rufl.h"
|
2004-02-25 18:12:58 +03:00
|
|
|
#include "oslib/colourtrans.h"
|
2004-04-10 22:19:35 +04:00
|
|
|
#include "oslib/osfile.h"
|
2008-07-27 02:29:15 +04:00
|
|
|
#include "oslib/osspriteop.h"
|
2014-10-13 14:56:31 +04:00
|
|
|
|
|
|
|
#include "utils/nsoption.h"
|
|
|
|
#include "utils/log.h"
|
2007-05-31 02:39:54 +04:00
|
|
|
#include "content/content.h"
|
2010-04-07 22:01:29 +04:00
|
|
|
#include "content/hlcache.h"
|
2007-05-31 02:39:54 +04:00
|
|
|
#include "content/urldb.h"
|
|
|
|
#include "desktop/plotters.h"
|
2011-02-20 01:17:54 +03:00
|
|
|
#include "desktop/thumbnail.h"
|
2007-05-31 02:39:54 +04:00
|
|
|
#include "image/bitmap.h"
|
2014-10-13 14:56:31 +04:00
|
|
|
|
2007-05-31 02:39:54 +04:00
|
|
|
#include "riscos/bitmap.h"
|
|
|
|
#include "riscos/gui.h"
|
2008-03-10 03:51:51 +03:00
|
|
|
#include "riscos/oslib_pre7.h"
|
2007-05-31 02:39:54 +04:00
|
|
|
#include "riscos/thumbnail.h"
|
|
|
|
#include "riscos/tinct.h"
|
2004-02-25 18:12:58 +03:00
|
|
|
|
2004-04-10 22:19:35 +04:00
|
|
|
/* Whether we can use 32bpp sprites
|
|
|
|
*/
|
|
|
|
static int thumbnail_32bpp_available = -1;
|
|
|
|
|
|
|
|
|
|
|
|
/* Sprite output context saving
|
|
|
|
*/
|
|
|
|
struct thumbnail_save_area {
|
|
|
|
osspriteop_save_area *save_area;
|
|
|
|
int context1;
|
|
|
|
int context2;
|
|
|
|
int context3;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Internal prototypes
|
|
|
|
*/
|
2005-06-23 21:24:23 +04:00
|
|
|
static osspriteop_area *thumbnail_create_8bpp(struct bitmap *bitmap);
|
2004-04-10 22:19:35 +04:00
|
|
|
static void thumbnail_test(void);
|
2005-06-23 21:24:23 +04:00
|
|
|
static struct thumbnail_save_area* thumbnail_switch_output(
|
|
|
|
osspriteop_area *sprite_area,
|
|
|
|
osspriteop_header *sprite_header);
|
2004-04-10 22:19:35 +04:00
|
|
|
static void thumbnail_restore_output(struct thumbnail_save_area *save_area);
|
2004-05-21 14:29:54 +04:00
|
|
|
|
2004-04-10 22:19:35 +04:00
|
|
|
|
2004-02-25 18:12:58 +03:00
|
|
|
/**
|
|
|
|
* Create a thumbnail of a page.
|
|
|
|
*
|
|
|
|
* \param content content structure to thumbnail
|
2005-06-23 21:24:23 +04:00
|
|
|
* \param bitmap the bitmap to draw to
|
2006-04-10 00:06:12 +04:00
|
|
|
* \param url the URL the thumbnail belongs to, or NULL
|
2004-02-25 18:12:58 +03:00
|
|
|
*/
|
2010-04-07 22:01:29 +04:00
|
|
|
bool thumbnail_create(hlcache_handle *content, struct bitmap *bitmap,
|
2012-10-11 14:20:02 +04:00
|
|
|
nsurl *url)
|
2005-09-08 00:22:33 +04:00
|
|
|
{
|
2004-04-10 22:19:35 +04:00
|
|
|
struct thumbnail_save_area *save_area;
|
2005-06-23 21:24:23 +04:00
|
|
|
osspriteop_area *sprite_area = NULL;
|
|
|
|
osspriteop_header *sprite_header = NULL;
|
2011-06-30 19:48:07 +04:00
|
|
|
struct redraw_context ctx = {
|
|
|
|
.interactive = false,
|
2011-12-24 02:39:25 +04:00
|
|
|
.background_images = true,
|
2011-06-30 19:48:07 +04:00
|
|
|
.plot = &ro_plotters
|
|
|
|
};
|
2005-06-23 21:24:23 +04:00
|
|
|
|
2005-09-08 00:22:33 +04:00
|
|
|
assert(content);
|
|
|
|
assert(bitmap);
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* check if we have access to 32bpp sprites natively */
|
|
|
|
if (thumbnail_32bpp_available == -1)
|
|
|
|
thumbnail_test();
|
|
|
|
|
|
|
|
/* if we don't support 32bpp sprites then we redirect to an 8bpp
|
|
|
|
* image and then convert back. */
|
|
|
|
if (thumbnail_32bpp_available != 1) {
|
2005-09-08 00:22:33 +04:00
|
|
|
sprite_area = thumbnail_create_8bpp(bitmap);
|
|
|
|
if (!sprite_area)
|
|
|
|
return false;
|
2005-06-23 21:24:23 +04:00
|
|
|
sprite_header = (osspriteop_header *)(sprite_area + 1);
|
2004-11-11 01:39:33 +03:00
|
|
|
} else {
|
2009-03-27 04:24:32 +03:00
|
|
|
const uint8_t *pixbufp = bitmap_get_buffer(bitmap);
|
2005-09-08 00:22:33 +04:00
|
|
|
if (!pixbufp || !bitmap->sprite_area)
|
|
|
|
return false;
|
2005-06-23 21:24:23 +04:00
|
|
|
sprite_area = bitmap->sprite_area;
|
|
|
|
sprite_header = (osspriteop_header *)(sprite_area + 1);
|
2004-11-11 01:39:33 +03:00
|
|
|
}
|
2004-05-21 14:29:54 +04:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* set up the plotters */
|
2004-11-03 02:23:07 +03:00
|
|
|
ro_plot_origin_x = 0;
|
2005-06-23 21:24:23 +04:00
|
|
|
ro_plot_origin_y = bitmap->height * 2;
|
2011-02-20 01:17:54 +03:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* switch output and redraw */
|
|
|
|
save_area = thumbnail_switch_output(sprite_area, sprite_header);
|
2005-09-08 00:22:33 +04:00
|
|
|
if (!save_area) {
|
2005-06-23 21:24:23 +04:00
|
|
|
if (thumbnail_32bpp_available != 1)
|
|
|
|
free(sprite_area);
|
|
|
|
return false;
|
2004-11-03 02:23:07 +03:00
|
|
|
}
|
2005-06-23 21:24:23 +04:00
|
|
|
rufl_invalidate_cache();
|
2008-03-10 03:51:51 +03:00
|
|
|
colourtrans_set_gcol(os_COLOUR_WHITE, colourtrans_SET_BG_GCOL,
|
2004-11-03 02:23:07 +03:00
|
|
|
os_ACTION_OVERWRITE, 0);
|
2011-02-20 01:17:54 +03:00
|
|
|
|
2011-06-30 19:48:07 +04:00
|
|
|
thumbnail_redraw(content, bitmap->width, bitmap->height, &ctx);
|
2011-02-20 01:17:54 +03:00
|
|
|
|
2004-11-03 02:23:07 +03:00
|
|
|
thumbnail_restore_output(save_area);
|
2005-02-20 16:19:19 +03:00
|
|
|
rufl_invalidate_cache();
|
2004-11-03 02:23:07 +03:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* if we changed to 8bpp then go back to 32bpp */
|
|
|
|
if (thumbnail_32bpp_available != 1) {
|
2009-03-27 04:24:32 +03:00
|
|
|
const uint8_t *pixbufp = bitmap_get_buffer(bitmap);
|
2014-05-31 02:11:09 +04:00
|
|
|
_kernel_oserror *error;
|
|
|
|
|
2005-09-08 00:22:33 +04:00
|
|
|
if (!pixbufp || !bitmap->sprite_area) {
|
|
|
|
free(sprite_area);
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-23 21:24:23 +04:00
|
|
|
error = _swix(Tinct_ConvertSprite, _INR(2,3),
|
|
|
|
sprite_header,
|
|
|
|
(osspriteop_header *)(bitmap->sprite_area + 1));
|
|
|
|
free(sprite_area);
|
|
|
|
if (error)
|
|
|
|
return false;
|
2004-04-11 18:30:18 +04:00
|
|
|
}
|
2005-09-08 00:22:33 +04:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* register the thumbnail with the URL */
|
|
|
|
if (url)
|
2006-04-10 03:21:13 +04:00
|
|
|
urldb_set_thumbnail(url, bitmap);
|
2005-06-23 21:24:23 +04:00
|
|
|
bitmap_modified(bitmap);
|
|
|
|
return true;
|
2004-04-10 22:19:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2005-06-23 21:24:23 +04:00
|
|
|
* Convert a bitmap to 8bpp.
|
2004-04-10 22:19:35 +04:00
|
|
|
*
|
2005-06-23 21:24:23 +04:00
|
|
|
* \param bitmap the bitmap to convert
|
|
|
|
* \return a sprite area containing an 8bpp sprite
|
|
|
|
*/
|
2005-09-08 00:22:33 +04:00
|
|
|
osspriteop_area *thumbnail_convert_8bpp(struct bitmap *bitmap)
|
|
|
|
{
|
2005-06-23 21:24:23 +04:00
|
|
|
struct thumbnail_save_area *save_area;
|
|
|
|
osspriteop_area *sprite_area = NULL;
|
|
|
|
osspriteop_header *sprite_header = NULL;
|
|
|
|
|
2006-03-24 06:44:37 +03:00
|
|
|
sprite_area = thumbnail_create_8bpp(bitmap);
|
2005-06-23 21:24:23 +04:00
|
|
|
if (!sprite_area)
|
|
|
|
return NULL;
|
|
|
|
sprite_header = (osspriteop_header *)(sprite_area + 1);
|
|
|
|
|
|
|
|
|
|
|
|
/* switch output and redraw */
|
|
|
|
save_area = thumbnail_switch_output(sprite_area, sprite_header);
|
|
|
|
if (save_area == NULL) {
|
|
|
|
if (thumbnail_32bpp_available != 1)
|
|
|
|
free(sprite_area);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
_swix(Tinct_Plot, _IN(2) | _IN(3) | _IN(4) | _IN(7),
|
|
|
|
(osspriteop_header *)(bitmap->sprite_area + 1),
|
|
|
|
0, 0,
|
|
|
|
tinct_ERROR_DIFFUSE);
|
|
|
|
thumbnail_restore_output(save_area);
|
2005-09-08 00:22:33 +04:00
|
|
|
|
2006-03-24 06:44:37 +03:00
|
|
|
if (sprite_header->image != sprite_header->mask) {
|
|
|
|
/* build the sprite mask from the alpha channel */
|
2009-03-27 04:24:32 +03:00
|
|
|
void *buf = bitmap_get_buffer(bitmap);
|
|
|
|
unsigned *dp = (unsigned *) buf;
|
2006-08-04 23:14:40 +04:00
|
|
|
if (!dp)
|
|
|
|
return sprite_area;
|
|
|
|
int w = bitmap_get_width(bitmap);
|
|
|
|
int h = bitmap_get_height(bitmap);
|
|
|
|
int dp_offset = bitmap_get_rowstride(bitmap) / 4 - w;
|
2006-03-24 06:44:37 +03:00
|
|
|
int mp_offset = ((sprite_header->width + 1) * 4) - w;
|
|
|
|
byte *mp = (byte*)sprite_header + sprite_header->mask;
|
|
|
|
bool alpha = ((unsigned)sprite_header->mode & 0x80000000U) != 0;
|
|
|
|
|
|
|
|
while (h-- > 0) {
|
|
|
|
int x = 0;
|
|
|
|
for(x = 0; x < w; x++) {
|
|
|
|
unsigned d = *dp++;
|
|
|
|
if (alpha)
|
|
|
|
*mp++ = (d >> 24) ^ 0xff;
|
|
|
|
else
|
|
|
|
*mp++ = (d < 0xff000000U) ? 0 : 0xff;
|
|
|
|
}
|
|
|
|
dp += dp_offset;
|
|
|
|
mp += mp_offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
return sprite_area;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an 8bpp canvas.
|
2004-04-10 22:19:35 +04:00
|
|
|
*
|
2005-06-23 21:24:23 +04:00
|
|
|
* \param bitmap the bitmap to clone the size of
|
|
|
|
* \return a sprite area containing an 8bpp sprite
|
2004-04-10 22:19:35 +04:00
|
|
|
*/
|
2005-09-08 00:22:33 +04:00
|
|
|
osspriteop_area *thumbnail_create_8bpp(struct bitmap *bitmap)
|
|
|
|
{
|
2006-03-24 06:44:37 +03:00
|
|
|
unsigned image_size = ((bitmap->width + 3) & ~3) * bitmap->height;
|
|
|
|
bool opaque = bitmap_get_opaque(bitmap);
|
2005-06-23 21:24:23 +04:00
|
|
|
osspriteop_header *sprite_header = NULL;
|
2006-03-24 06:44:37 +03:00
|
|
|
osspriteop_area *sprite_area = NULL;
|
|
|
|
unsigned area_size;
|
2004-04-10 22:19:35 +04:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* clone the sprite */
|
|
|
|
area_size = sizeof(osspriteop_area) +
|
|
|
|
sizeof(osspriteop_header) +
|
2006-03-24 06:44:37 +03:00
|
|
|
image_size +
|
2005-06-23 21:24:23 +04:00
|
|
|
2048;
|
2006-03-24 06:44:37 +03:00
|
|
|
|
|
|
|
if (!opaque) area_size += image_size;
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
sprite_area = (osspriteop_area *)malloc(area_size);
|
|
|
|
if (!sprite_area) {
|
|
|
|
LOG(("no memory for malloc()"));
|
2004-04-10 22:19:35 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
sprite_area->size = area_size;
|
|
|
|
sprite_area->sprite_count = 1;
|
|
|
|
sprite_area->first = 16;
|
|
|
|
sprite_area->used = area_size;
|
|
|
|
sprite_header = (osspriteop_header *)(sprite_area + 1);
|
|
|
|
sprite_header->size = area_size - sizeof(osspriteop_area);
|
|
|
|
memset(sprite_header->name, 0x00, 12);
|
2005-06-23 21:24:23 +04:00
|
|
|
strcpy(sprite_header->name, "bitmap");
|
2004-04-10 22:19:35 +04:00
|
|
|
sprite_header->left_bit = 0;
|
2005-06-23 21:24:23 +04:00
|
|
|
sprite_header->height = bitmap->height - 1;
|
|
|
|
sprite_header->mode = os_MODE8BPP90X90;
|
|
|
|
sprite_header->right_bit = ((bitmap->width << 3) - 1) & 31;
|
|
|
|
sprite_header->width = ((bitmap->width + 3) >> 2) - 1;
|
|
|
|
sprite_header->image = sizeof(osspriteop_header) + 2048;
|
|
|
|
sprite_header->mask = sizeof(osspriteop_header) + 2048;
|
2006-03-24 06:44:37 +03:00
|
|
|
if (!opaque) sprite_header->mask += image_size;
|
2005-06-23 21:24:23 +04:00
|
|
|
|
|
|
|
/* create the palette. we don't read the necessary size like
|
|
|
|
* we really should as we know it's going to have 256 entries
|
|
|
|
* of 8 bytes = 2048. */
|
|
|
|
xcolourtrans_read_palette((osspriteop_area *)os_MODE8BPP90X90,
|
|
|
|
(osspriteop_id)0,
|
2004-04-10 22:19:35 +04:00
|
|
|
(os_palette *)(sprite_header + 1), 2048,
|
2005-06-23 21:24:23 +04:00
|
|
|
(colourtrans_palette_flags)(1 << 1), 0);
|
2004-04-10 22:19:35 +04:00
|
|
|
return sprite_area;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/**
|
|
|
|
* Check to see whether 32bpp sprites are available.
|
|
|
|
*
|
|
|
|
* Rather than using Wimp_ReadSysInfo we test if 32bpp sprites are available
|
|
|
|
* in case the user has a 3rd party patch to enable them.
|
2004-04-10 22:19:35 +04:00
|
|
|
*/
|
2005-09-08 00:22:33 +04:00
|
|
|
static void thumbnail_test(void)
|
|
|
|
{
|
2004-04-10 22:19:35 +04:00
|
|
|
unsigned int area_size;
|
|
|
|
osspriteop_area *sprite_area;
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* try to create a 1x1 32bpp sprite */
|
2004-04-10 22:19:35 +04:00
|
|
|
area_size = sizeof(osspriteop_area) +
|
|
|
|
sizeof(osspriteop_header) + sizeof(int);
|
|
|
|
if ((sprite_area = (osspriteop_area *)malloc(area_size)) == NULL) {
|
|
|
|
LOG(("Insufficient memory to perform sprite test."));
|
2004-02-25 18:12:58 +03:00
|
|
|
return;
|
|
|
|
}
|
2004-04-10 22:19:35 +04:00
|
|
|
sprite_area->size = area_size + 1;
|
|
|
|
sprite_area->sprite_count = 0;
|
|
|
|
sprite_area->first = 16;
|
|
|
|
sprite_area->used = 16;
|
|
|
|
if (xosspriteop_create_sprite(osspriteop_NAME, sprite_area,
|
2005-06-23 21:24:23 +04:00
|
|
|
"test", false, 1, 1, (os_mode)tinct_SPRITE_MODE))
|
2004-04-10 22:19:35 +04:00
|
|
|
thumbnail_32bpp_available = 0;
|
2005-06-23 21:24:23 +04:00
|
|
|
else
|
2004-04-10 22:19:35 +04:00
|
|
|
thumbnail_32bpp_available = 1;
|
|
|
|
free(sprite_area);
|
|
|
|
}
|
2004-02-25 18:12:58 +03:00
|
|
|
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/**
|
|
|
|
* Switches output to the specified sprite and returns the previous context.
|
|
|
|
*/
|
|
|
|
static struct thumbnail_save_area* thumbnail_switch_output(
|
|
|
|
osspriteop_area *sprite_area,
|
2005-09-08 00:22:33 +04:00
|
|
|
osspriteop_header *sprite_header)
|
|
|
|
{
|
2004-04-10 22:19:35 +04:00
|
|
|
struct thumbnail_save_area *save_area;
|
|
|
|
int size;
|
2004-05-21 14:29:54 +04:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* create a save area */
|
2004-04-10 22:19:35 +04:00
|
|
|
save_area = calloc(sizeof(struct thumbnail_save_area), 1);
|
|
|
|
if (save_area == NULL) return NULL;
|
2004-05-21 14:29:54 +04:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* allocate OS_SpriteOp save area */
|
2004-04-10 22:19:35 +04:00
|
|
|
if (xosspriteop_read_save_area_size(osspriteop_PTR, sprite_area,
|
|
|
|
(osspriteop_id)sprite_header, &size)) {
|
|
|
|
free(save_area);
|
|
|
|
return NULL;
|
|
|
|
}
|
2004-02-25 18:12:58 +03:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* create the save area */
|
2004-04-10 22:19:35 +04:00
|
|
|
save_area->save_area = malloc((unsigned)size);
|
|
|
|
if (save_area->save_area == NULL) {
|
2004-04-11 18:30:18 +04:00
|
|
|
free(save_area);
|
2004-04-10 22:19:35 +04:00
|
|
|
return NULL;
|
2004-02-25 18:12:58 +03:00
|
|
|
}
|
2004-04-10 22:19:35 +04:00
|
|
|
save_area->save_area->a[0] = 0;
|
2004-02-26 20:23:02 +03:00
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/* switch output to sprite */
|
2004-04-10 22:19:35 +04:00
|
|
|
if (xosspriteop_switch_output_to_sprite(osspriteop_PTR, sprite_area,
|
|
|
|
(osspriteop_id)sprite_header, save_area->save_area,
|
|
|
|
0, &save_area->context1, &save_area->context2,
|
|
|
|
&save_area->context3)) {
|
|
|
|
free(save_area->save_area);
|
|
|
|
free(save_area);
|
|
|
|
return NULL;
|
|
|
|
}
|
2004-04-11 18:30:18 +04:00
|
|
|
return save_area;
|
2004-04-10 22:19:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-23 21:24:23 +04:00
|
|
|
/**
|
|
|
|
* Restores output to the specified context, and destroys it.
|
|
|
|
*/
|
2005-09-08 00:22:33 +04:00
|
|
|
static void thumbnail_restore_output(struct thumbnail_save_area *save_area)
|
|
|
|
{
|
2005-06-23 21:24:23 +04:00
|
|
|
/* we don't care if we err, as there's nothing we can do about it */
|
2004-04-10 22:19:35 +04:00
|
|
|
xosspriteop_switch_output_to_sprite(osspriteop_PTR,
|
|
|
|
(osspriteop_area *)save_area->context1,
|
|
|
|
(osspriteop_id)save_area->context2,
|
|
|
|
(osspriteop_save_area *)save_area->context3,
|
|
|
|
0, 0, 0, 0);
|
|
|
|
free(save_area->save_area);
|
2004-02-26 20:23:02 +03:00
|
|
|
free(save_area);
|
2004-02-25 18:12:58 +03:00
|
|
|
}
|