gimp/app/xcf/xcf-save.c

1215 lines
32 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* 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 "config.h"
#include <stdio.h>
#include <string.h> /* strcpy, strlen */
#include <glib-object.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "core/core-types.h"
#include "base/tile.h"
#include "base/tile-manager.h"
#include "base/tile-manager-private.h"
#include "core/gimpchannel.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-mask.h"
#include "core/gimplayer.h"
#include "core/gimplayer-floating-sel.h"
#include "core/gimplayermask.h"
#include "core/gimplist.h"
#include "core/gimpparasitelist.h"
#include "core/gimpunit.h"
#include "xcf-private.h"
#include "xcf-read.h"
#include "xcf-seek.h"
#include "xcf-write.h"
#include "path.h"
#include "pathP.h"
static void xcf_save_image_props (XcfInfo *info,
GimpImage *gimage);
static void xcf_save_layer_props (XcfInfo *info,
GimpImage *gimage,
GimpLayer *layer);
static void xcf_save_channel_props (XcfInfo *info,
GimpImage *gimage,
GimpChannel *channel);
static void xcf_save_prop (XcfInfo *info,
GimpImage *gimage,
PropType prop_type,
...);
static void xcf_save_layer (XcfInfo *info,
GimpImage *gimage,
GimpLayer *layer);
static void xcf_save_channel (XcfInfo *info,
GimpImage *gimage,
GimpChannel *channel);
static void xcf_save_hierarchy (XcfInfo *info,
TileManager *tiles);
static void xcf_save_level (XcfInfo *info,
TileManager *tiles);
static void xcf_save_tile (XcfInfo *info,
Tile *tile);
static void xcf_save_tile_rle (XcfInfo *info,
Tile *tile,
guchar *rlebuf);
void
xcf_save_choose_format (XcfInfo *info,
GimpImage *gimage)
{
gint save_version = 0; /* default to oldest */
if (gimage->cmap)
save_version = 1; /* need version 1 for colormaps */
info->file_version = save_version;
}
gint
xcf_save_image (XcfInfo *info,
GimpImage *gimage)
{
GimpLayer *layer;
GimpLayer *floating_layer;
GimpChannel *channel;
guint32 saved_pos;
guint32 offset;
guint nlayers;
guint nchannels;
GList *list;
gboolean have_selection;
gint t1, t2, t3, t4;
gchar version_tag[14];
floating_layer = gimp_image_floating_sel (gimage);
if (floating_layer)
floating_sel_relax (floating_layer, FALSE);
/* write out the tag information for the image */
if (info->file_version > 0)
{
sprintf (version_tag, "gimp xcf v%03d", info->file_version);
}
else
{
strcpy (version_tag, "gimp xcf file");
}
info->cp += xcf_write_int8 (info->fp, (guint8*) version_tag, 14);
/* write out the width, height and image type information for the image */
info->cp += xcf_write_int32 (info->fp, (guint32*) &gimage->width, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &gimage->height, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &gimage->base_type, 1);
/* determine the number of layers and channels in the image */
nlayers = (guint) gimp_container_num_children (gimage->layers);
nchannels = (guint) gimp_container_num_children (gimage->channels);
/* check and see if we have to save out the selection */
have_selection = gimp_image_mask_bounds (gimage, &t1, &t2, &t3, &t4);
if (have_selection)
nchannels += 1;
/* write the property information for the image.
*/
xcf_save_image_props (info, gimage);
/* save the current file position as it is the start of where
* we place the layer offset information.
*/
saved_pos = info->cp;
/* seek to after the offset lists */
xcf_seek_pos (info, info->cp + (nlayers + nchannels + 2) * 4);
for (list = GIMP_LIST (gimage->layers)->list;
list;
list = g_list_next (list))
{
layer = (GimpLayer *) list->data;
/* save the start offset of where we are writing
* out the next layer.
*/
offset = info->cp;
/* write out the layer. */
xcf_save_layer (info, gimage, layer);
/* seek back to where we are to write out the next
* layer offset and write it out.
*/
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
/* increment the location we are to write out the
* next offset.
*/
saved_pos = info->cp;
/* seek to the end of the file which is where
* we will write out the next layer.
*/
xcf_seek_end (info);
}
/* write out a '0' offset position to indicate the end
* of the layer offsets.
*/
offset = 0;
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
saved_pos = info->cp;
xcf_seek_end (info);
list = GIMP_LIST (gimage->channels)->list;
while (list || have_selection)
{
if (list)
{
channel = (GimpChannel *) list->data;
list = g_list_next (list);
}
else
{
channel = gimage->selection_mask;
have_selection = FALSE;
}
/* save the start offset of where we are writing
* out the next channel.
*/
offset = info->cp;
/* write out the layer. */
xcf_save_channel (info, gimage, channel);
/* seek back to where we are to write out the next
* channel offset and write it out.
*/
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
/* increment the location we are to write out the
* next offset.
*/
saved_pos = info->cp;
/* seek to the end of the file which is where
* we will write out the next channel.
*/
xcf_seek_end (info);
}
/* write out a '0' offset position to indicate the end
* of the channel offsets.
*/
offset = 0;
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
saved_pos = info->cp;
if (floating_layer)
floating_sel_rigor (floating_layer, FALSE);
return !ferror(info->fp);
}
static void
xcf_save_image_props (XcfInfo *info,
GimpImage *gimage)
{
/* check and see if we should save the colormap property */
if (gimage->cmap)
xcf_save_prop (info, gimage,
PROP_COLORMAP, gimage->num_cols, gimage->cmap);
if (info->compression != COMPRESS_NONE)
xcf_save_prop (info, gimage, PROP_COMPRESSION, info->compression);
if (gimage->guides)
xcf_save_prop (info, gimage, PROP_GUIDES, gimage->guides);
xcf_save_prop (info, gimage, PROP_RESOLUTION,
gimage->xresolution, gimage->yresolution);
xcf_save_prop (info, gimage, PROP_TATTOO, gimage->tattoo_state);
if (gimp_parasite_list_length (gimage->parasites) > 0)
xcf_save_prop (info, gimage, PROP_PARASITES, gimage->parasites);
if (gimage->unit < _gimp_unit_get_number_of_built_in_units (gimage->gimp))
xcf_save_prop (info, gimage, PROP_UNIT, gimage->unit);
xcf_save_prop (info, gimage, PROP_PATHS, gimage->paths);
if (gimage->unit >= _gimp_unit_get_number_of_built_in_units (gimage->gimp))
xcf_save_prop (info, gimage, PROP_USER_UNIT, gimage->unit);
xcf_save_prop (info, gimage, PROP_END);
}
static void
xcf_save_layer_props (XcfInfo *info,
GimpImage *gimage,
GimpLayer *layer)
{
if (layer == gimp_image_get_active_layer (gimage))
xcf_save_prop (info, gimage, PROP_ACTIVE_LAYER);
if (layer == gimp_image_floating_sel (gimage))
{
info->floating_sel_drawable = layer->fs.drawable;
xcf_save_prop (info, gimage, PROP_FLOATING_SELECTION);
}
xcf_save_prop (info, gimage, PROP_OPACITY, layer->opacity);
xcf_save_prop (info, gimage, PROP_VISIBLE,
gimp_drawable_get_visible (GIMP_DRAWABLE (layer)));
xcf_save_prop (info, gimage, PROP_LINKED, layer->linked);
xcf_save_prop (info, gimage, PROP_PRESERVE_TRANSPARENCY,
layer->preserve_trans);
if (layer->mask)
{
xcf_save_prop (info, gimage, PROP_APPLY_MASK, layer->mask->apply_mask);
xcf_save_prop (info, gimage, PROP_EDIT_MASK, layer->mask->edit_mask);
xcf_save_prop (info, gimage, PROP_SHOW_MASK, layer->mask->show_mask);
}
else
{
xcf_save_prop (info, gimage, PROP_APPLY_MASK, FALSE);
xcf_save_prop (info, gimage, PROP_EDIT_MASK, FALSE);
xcf_save_prop (info, gimage, PROP_SHOW_MASK, FALSE);
}
xcf_save_prop (info, gimage, PROP_OFFSETS,
GIMP_DRAWABLE (layer)->offset_x,
GIMP_DRAWABLE (layer)->offset_y);
xcf_save_prop (info, gimage, PROP_MODE, layer->mode);
xcf_save_prop (info, gimage, PROP_TATTOO, GIMP_ITEM (layer)->tattoo);
if (gimp_parasite_list_length (GIMP_ITEM (layer)->parasites) > 0)
xcf_save_prop (info, gimage, PROP_PARASITES,
GIMP_ITEM (layer)->parasites);
xcf_save_prop (info, gimage, PROP_END);
}
static void
xcf_save_channel_props (XcfInfo *info,
GimpImage *gimage,
GimpChannel *channel)
{
guchar col[3];
if (channel == gimp_image_get_active_channel (gimage))
xcf_save_prop (info, gimage, PROP_ACTIVE_CHANNEL);
if (channel == gimage->selection_mask)
xcf_save_prop (info, gimage, PROP_SELECTION);
xcf_save_prop (info, gimage, PROP_OPACITY, channel->color.a);
xcf_save_prop (info, gimage, PROP_VISIBLE,
gimp_drawable_get_visible (GIMP_DRAWABLE (channel)));
xcf_save_prop (info, gimage, PROP_SHOW_MASKED, channel->show_masked);
gimp_rgb_get_uchar (&channel->color, &col[0], &col[1], &col[2]);
xcf_save_prop (info, gimage, PROP_COLOR, col);
xcf_save_prop (info, gimage, PROP_TATTOO, GIMP_ITEM (channel)->tattoo);
if (gimp_parasite_list_length (GIMP_ITEM (channel)->parasites) > 0)
xcf_save_prop (info, gimage, PROP_PARASITES,
GIMP_ITEM (channel)->parasites);
xcf_save_prop (info, gimage, PROP_END);
}
static void
xcf_save_parasite (gchar *key,
GimpParasite *parasite,
XcfInfo *info)
{
if (gimp_parasite_is_persistent (parasite))
{
info->cp += xcf_write_string (info->fp, &parasite->name, 1);
info->cp += xcf_write_int32 (info->fp, &parasite->flags, 1);
info->cp += xcf_write_int32 (info->fp, &parasite->size, 1);
info->cp += xcf_write_int8 (info->fp, parasite->data, parasite->size);
}
}
static void
xcf_save_bz_point (PathPoint *bpt,
XcfInfo *info)
{
gfloat xfloat = bpt->x;
gfloat yfloat = bpt->y;
/* (all gint)
* type
* x
* y
*/
info->cp += xcf_write_int32 (info->fp, &bpt->type, 1);
info->cp += xcf_write_float (info->fp, &xfloat, 1);
info->cp += xcf_write_float (info->fp, &yfloat, 1);
}
static void
xcf_save_path (Path *bzp,
XcfInfo *info)
{
guint8 state = bzp->state;
guint32 num_points;
guint32 closed;
guint32 version;
/*
* name (string)
* locked (gint)
* state (gchar)
* closed (gint)
* number points (gint)
* number paths (gint unused)
* then each point.
*/
info->cp += xcf_write_string (info->fp, &bzp->name, 1);
info->cp += xcf_write_int32 (info->fp, &bzp->locked, 1);
info->cp += xcf_write_int8 (info->fp, &state, 1);
closed = bzp->closed;
info->cp += xcf_write_int32 (info->fp, &closed, 1);
num_points = g_slist_length (bzp->path_details);
info->cp += xcf_write_int32 (info->fp, &num_points, 1);
version = 3;
info->cp += xcf_write_int32 (info->fp, &version, 1);
info->cp += xcf_write_int32 (info->fp, &bzp->pathtype, 1);
info->cp += xcf_write_int32 (info->fp, &bzp->tattoo, 1);
g_slist_foreach (bzp->path_details, (GFunc) xcf_save_bz_point, info);
}
static void
xcf_save_bzpaths (PathList *paths,
XcfInfo *info)
{
guint32 num_paths;
/* Write out the following:-
*
* last_selected_row (gint)
* number_of_paths (gint)
*
* then each path:-
*/
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &paths->last_selected_row, 1);
num_paths = g_slist_length (paths->bz_paths);
info->cp += xcf_write_int32 (info->fp, &num_paths, 1);
g_slist_foreach (paths->bz_paths, (GFunc) xcf_save_path, info);
}
static void
xcf_save_prop (XcfInfo *info,
GimpImage *gimage,
PropType prop_type,
...)
{
guint32 size;
va_list args;
va_start (args, prop_type);
switch (prop_type)
{
case PROP_END:
size = 0;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
break;
case PROP_COLORMAP:
{
guint32 ncolors;
guchar *colors;
ncolors = va_arg (args, guint32);
colors = va_arg (args, guchar*);
size = 4 + ncolors;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &ncolors, 1);
info->cp += xcf_write_int8 (info->fp, colors, ncolors * 3);
}
break;
case PROP_ACTIVE_LAYER:
case PROP_ACTIVE_CHANNEL:
case PROP_SELECTION:
size = 0;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
break;
case PROP_FLOATING_SELECTION:
{
guint32 dummy;
dummy = 0;
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->floating_sel_offset = info->cp;
info->cp += xcf_write_int32 (info->fp, &dummy, 1);
}
break;
case PROP_OPACITY:
{
gdouble opacity;
guint32 uint_opacity;
opacity = va_arg (args, gdouble);
uint_opacity = opacity * 255.999;
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &uint_opacity, 1);
}
break;
case PROP_MODE:
{
gint32 mode;
mode = va_arg (args, gint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &mode, 1);
}
break;
case PROP_VISIBLE:
{
guint32 visible;
visible = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &visible, 1);
}
break;
case PROP_LINKED:
{
guint32 linked;
linked = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &linked, 1);
}
break;
case PROP_PRESERVE_TRANSPARENCY:
{
guint32 preserve_trans;
preserve_trans = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &preserve_trans, 1);
}
break;
case PROP_APPLY_MASK:
{
guint32 apply_mask;
apply_mask = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &apply_mask, 1);
}
break;
case PROP_EDIT_MASK:
{
guint32 edit_mask;
edit_mask = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &edit_mask, 1);
}
break;
case PROP_SHOW_MASK:
{
guint32 show_mask;
show_mask = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &show_mask, 1);
}
break;
case PROP_SHOW_MASKED:
{
guint32 show_masked;
show_masked = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &show_masked, 1);
}
break;
case PROP_OFFSETS:
{
gint32 offsets[2];
offsets[0] = va_arg (args, gint32);
offsets[1] = va_arg (args, gint32);
size = 8;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) offsets, 2);
}
break;
case PROP_COLOR:
{
guchar *color;
color = va_arg (args, guchar*);
size = 3;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int8 (info->fp, color, 3);
}
break;
case PROP_COMPRESSION:
{
guint8 compression;
compression = (guint8) va_arg (args, guint32);
size = 1;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int8 (info->fp, &compression, 1);
}
break;
case PROP_GUIDES:
{
GList *guides;
GimpGuide *guide;
gint32 position;
gint8 orientation;
gint nguides;
guides = va_arg (args, GList *);
nguides = g_list_length (guides);
size = nguides * (4 + 1);
info->cp += xcf_write_int32 (info->fp, (guint32 *) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
for (; guides; guides = g_list_next (guides))
{
guide = (GimpGuide *) guides->data;
position = guide->position;
orientation = guide->orientation;
info->cp += xcf_write_int32 (info->fp, (guint32 *) &position, 1);
info->cp += xcf_write_int8 (info->fp, (guint8 *) &orientation, 1);
}
}
break;
case PROP_RESOLUTION:
{
gfloat xresolution, yresolution;
/* we pass in floats,
but they are promoted to double by the compiler */
xresolution = va_arg (args, double);
yresolution = va_arg (args, double);
size = 4*2;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_float (info->fp, &xresolution, 1);
info->cp += xcf_write_float (info->fp, &yresolution, 1);
}
break;
case PROP_TATTOO:
{
guint32 tattoo;
tattoo = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &tattoo, 1);
}
break;
case PROP_PARASITES:
{
GimpParasiteList *list;
guint32 base, length;
long pos;
list = va_arg (args, GimpParasiteList *);
if (gimp_parasite_list_persistent_length (list) > 0)
{
info->cp += xcf_write_int32 (info->fp, (guint32 *) &prop_type, 1);
/* because we don't know how much room the parasite list will take
* we save the file position and write the length later
*/
pos = info->cp;
info->cp += xcf_write_int32 (info->fp, &length, 1);
base = info->cp;
gimp_parasite_list_foreach (list,
(GHFunc) xcf_save_parasite, info);
length = info->cp - base;
/* go back to the saved position and write the length */
xcf_seek_pos (info, pos);
xcf_write_int32 (info->fp, &length, 1);
xcf_seek_end (info);
}
}
break;
case PROP_UNIT:
{
guint32 unit;
unit = va_arg (args, guint32);
size = 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_int32 (info->fp, &unit, 1);
}
break;
case PROP_PATHS:
{
PathList *paths_list;
guint32 base, length;
long pos;
paths_list = va_arg (args, PathList *);
if (paths_list)
{
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
/* because we don't know how much room the paths list will take
we save the file position and write the length later
*/
pos = info->cp;
info->cp += xcf_write_int32 (info->fp, &length, 1);
base = info->cp;
xcf_save_bzpaths (paths_list, info);
length = info->cp - base;
/* go back to the saved position and write the length */
xcf_seek_pos (info, pos);
xcf_write_int32 (info->fp, &length, 1);
xcf_seek_end (info);
}
}
break;
case PROP_USER_UNIT:
{
GimpUnit unit;
const gchar *unit_strings[5];
gfloat factor;
guint32 digits;
unit = va_arg (args, guint32);
/* write the entire unit definition */
unit_strings[0] = _gimp_unit_get_identifier (gimage->gimp, unit);
factor = _gimp_unit_get_factor (gimage->gimp, unit);
digits = _gimp_unit_get_digits (gimage->gimp, unit);
unit_strings[1] = _gimp_unit_get_symbol (gimage->gimp, unit);
unit_strings[2] = _gimp_unit_get_abbreviation (gimage->gimp, unit);
unit_strings[3] = _gimp_unit_get_singular (gimage->gimp, unit);
unit_strings[4] = _gimp_unit_get_plural (gimage->gimp, unit);
size =
2 * 4 +
strlen (unit_strings[0]) ? strlen (unit_strings[0]) + 5 : 4 +
strlen (unit_strings[1]) ? strlen (unit_strings[1]) + 5 : 4 +
strlen (unit_strings[2]) ? strlen (unit_strings[2]) + 5 : 4 +
strlen (unit_strings[3]) ? strlen (unit_strings[3]) + 5 : 4 +
strlen (unit_strings[4]) ? strlen (unit_strings[4]) + 5 : 4;
info->cp += xcf_write_int32 (info->fp, (guint32*) &prop_type, 1);
info->cp += xcf_write_int32 (info->fp, &size, 1);
info->cp += xcf_write_float (info->fp, &factor, 1);
info->cp += xcf_write_int32 (info->fp, &digits, 1);
info->cp += xcf_write_string (info->fp, (gchar **) unit_strings, 5);
}
break;
}
va_end (args);
}
static void
xcf_save_layer (XcfInfo *info,
GimpImage *gimage,
GimpLayer *layer)
{
guint32 saved_pos;
guint32 offset;
/* check and see if this is the drawable that the floating
* selection is attached to.
*/
if (GIMP_DRAWABLE(layer) == info->floating_sel_drawable)
{
saved_pos = info->cp;
xcf_seek_pos (info, info->floating_sel_offset);
info->cp += xcf_write_int32 (info->fp, &saved_pos, 1);
xcf_seek_pos (info, saved_pos);
}
/* write out the width, height and image type information for the layer */
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &GIMP_DRAWABLE(layer)->width, 1);
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &GIMP_DRAWABLE(layer)->height, 1);
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &GIMP_DRAWABLE(layer)->type, 1);
/* write out the layers name */
info->cp += xcf_write_string (info->fp, &GIMP_OBJECT (layer)->name, 1);
/* write out the layer properties */
xcf_save_layer_props (info, gimage, layer);
/* save the current position which is where the hierarchy offset
* will be stored.
*/
saved_pos = info->cp;
/* write out the layer tile hierarchy */
xcf_seek_pos (info, info->cp + 8);
offset = info->cp;
xcf_save_hierarchy (info, GIMP_DRAWABLE(layer)->tiles);
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
saved_pos = info->cp;
/* write out the layer mask */
if (layer->mask)
{
xcf_seek_end (info);
offset = info->cp;
xcf_save_channel (info, gimage, GIMP_CHANNEL(layer->mask));
}
else
offset = 0;
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
}
static void
xcf_save_channel (XcfInfo *info,
GimpImage *gimage,
GimpChannel *channel)
{
guint32 saved_pos;
guint32 offset;
/* check and see if this is the drawable that the floating
* selection is attached to.
*/
if (GIMP_DRAWABLE (channel) == info->floating_sel_drawable)
{
saved_pos = info->cp;
xcf_seek_pos (info, info->floating_sel_offset);
info->cp += xcf_write_int32 (info->fp, (guint32*) &info->cp, 1);
xcf_seek_pos (info, saved_pos);
}
/* write out the width and height information for the channel */
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &GIMP_DRAWABLE(channel)->width, 1);
info->cp +=
xcf_write_int32 (info->fp, (guint32*) &GIMP_DRAWABLE(channel)->height, 1);
/* write out the channels name */
info->cp += xcf_write_string (info->fp, &GIMP_OBJECT (channel)->name, 1);
/* write out the channel properties */
xcf_save_channel_props (info, gimage, channel);
/* save the current position which is where the hierarchy offset
* will be stored.
*/
saved_pos = info->cp;
/* write out the channel tile hierarchy */
xcf_seek_pos (info, info->cp + 4);
offset = info->cp;
xcf_save_hierarchy (info, GIMP_DRAWABLE (channel)->tiles);
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
saved_pos = info->cp;
}
static gint
xcf_calc_levels (gint size,
gint tile_size)
{
int levels;
levels = 1;
while (size > tile_size)
{
size /= 2;
levels += 1;
}
return levels;
}
static void
xcf_save_hierarchy (XcfInfo *info,
TileManager *tiles)
{
guint32 saved_pos;
guint32 offset;
guint32 width;
guint32 height;
guint32 bpp;
gint i;
gint nlevels;
gint tmp1, tmp2;
width = tile_manager_width (tiles);
height = tile_manager_height (tiles);
bpp = tile_manager_bpp (tiles);
info->cp += xcf_write_int32 (info->fp, (guint32*) &width, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &height, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &bpp, 1);
saved_pos = info->cp;
tmp1 = xcf_calc_levels (width, TILE_WIDTH);
tmp2 = xcf_calc_levels (height, TILE_HEIGHT);
nlevels = MAX (tmp1, tmp2);
xcf_seek_pos (info, info->cp + (1 + nlevels) * 4);
for (i = 0; i < nlevels; i++)
{
offset = info->cp;
if (i == 0)
{
/* write out the level. */
xcf_save_level (info, tiles);
}
else
{
/* fake an empty level */
tmp1 = 0;
width /= 2;
height /= 2;
info->cp += xcf_write_int32 (info->fp, (guint32*) &width, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &height, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &tmp1, 1);
}
/* seek back to where we are to write out the next
* level offset and write it out.
*/
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
/* increment the location we are to write out the
* next offset.
*/
saved_pos = info->cp;
/* seek to the end of the file which is where
* we will write out the next level.
*/
xcf_seek_end (info);
}
/* write out a '0' offset position to indicate the end
* of the level offsets.
*/
offset = 0;
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
}
static void
xcf_save_level (XcfInfo *info,
TileManager *level)
{
guint32 saved_pos;
guint32 offset;
guint32 width;
guint32 height;
guint ntiles;
gint i;
guchar *rlebuf;
width = tile_manager_width (level);
height = tile_manager_height (level);
info->cp += xcf_write_int32 (info->fp, (guint32*) &width, 1);
info->cp += xcf_write_int32 (info->fp, (guint32*) &height, 1);
saved_pos = info->cp;
/* allocate a temporary buffer to store the rle data before it is
written to disk */
rlebuf =
g_malloc (TILE_WIDTH * TILE_HEIGHT * tile_manager_bpp (level) * 1.5);
if (level->tiles)
{
ntiles = level->ntile_rows * level->ntile_cols;
xcf_seek_pos (info, info->cp + (ntiles + 1) * 4);
for (i = 0; i < ntiles; i++)
{
/* save the start offset of where we are writing
* out the next tile.
*/
offset = info->cp;
/* write out the tile. */
switch (info->compression)
{
case COMPRESS_NONE:
xcf_save_tile (info, level->tiles[i]);
break;
case COMPRESS_RLE:
xcf_save_tile_rle (info, level->tiles[i], rlebuf);
break;
case COMPRESS_ZLIB:
g_error ("xcf: zlib compression unimplemented");
break;
case COMPRESS_FRACTAL:
g_error ("xcf: fractal compression unimplemented");
break;
}
/* seek back to where we are to write out the next
* tile offset and write it out.
*/
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
/* increment the location we are to write out the
* next offset.
*/
saved_pos = info->cp;
/* seek to the end of the file which is where
* we will write out the next tile.
*/
xcf_seek_end (info);
}
}
g_free (rlebuf);
/* write out a '0' offset position to indicate the end
* of the level offsets.
*/
offset = 0;
xcf_seek_pos (info, saved_pos);
info->cp += xcf_write_int32 (info->fp, &offset, 1);
}
static void
xcf_save_tile (XcfInfo *info,
Tile *tile)
{
tile_lock (tile);
info->cp += xcf_write_int8 (info->fp, tile_data_pointer (tile, 0, 0),
tile_size (tile));
tile_release (tile, FALSE);
}
static void
xcf_save_tile_rle (XcfInfo *info,
Tile *tile,
guchar *rlebuf)
{
guchar *data, *t;
unsigned int last;
gint state;
gint length;
gint count;
gint size;
gint bpp;
gint i, j;
gint len = 0;
tile_lock (tile);
bpp = tile_bpp (tile);
for (i = 0; i < bpp; i++)
{
data = (guchar*) tile_data_pointer (tile, 0, 0) + i;
state = 0;
length = 0;
count = 0;
size = tile_ewidth(tile) * tile_eheight(tile);
last = -1;
while (size > 0)
{
switch (state)
{
case 0:
/* in state 0 we try to find a long sequence of
* matching values.
*/
if ((length == 32768) ||
((size - length) <= 0) ||
((length > 1) && (last != *data)))
{
count += length;
if (length >= 128)
{
rlebuf[len++] = 127;
rlebuf[len++] = (length >> 8);
rlebuf[len++] = length & 0x00FF;
rlebuf[len++] = last;
}
else
{
rlebuf[len++] = length - 1;
rlebuf[len++] = last;
}
size -= length;
length = 0;
}
else if ((length == 1) && (last != *data))
state = 1;
break;
case 1:
/* in state 1 we try and find a long sequence of
* non-matching values.
*/
if ((length == 32768) ||
((size - length) == 0) ||
((length > 0) && (last == *data) &&
((size - length) == 1 || last == data[bpp])))
{
count += length;
state = 0;
if (length >= 128)
{
rlebuf[len++] = 255 - 127;
rlebuf[len++] = (length >> 8);
rlebuf[len++] = length & 0x00FF;
}
else
{
rlebuf[len++] = 255 - (length - 1);
}
t = data - length * bpp;
for (j = 0; j < length; j++)
{
rlebuf[len++] = *t;
t += bpp;
}
size -= length;
length = 0;
}
break;
}
if (size > 0) {
length += 1;
last = *data;
data += bpp;
}
}
if (count != (tile_ewidth (tile) * tile_eheight (tile)))
g_message ("xcf: uh oh! xcf rle tile saving error: %d", count);
}
info->cp += xcf_write_int8 (info->fp, rlebuf, len);
tile_release (tile, FALSE);
}