gimpbrush.c 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* 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.
 */
Sven Neumann's avatar
Sven Neumann committed
18 19 20

#include "config.h"

21 22 23
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
Michael Natterer's avatar
Michael Natterer committed
24

25 26 27 28 29 30 31
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>

32
#include <glib-object.h>
33 34

#ifdef G_OS_WIN32 /* gets defined by glib.h */
35 36 37 38 39 40 41 42 43
#include <io.h>
#endif

#ifndef _O_BINARY
#define _O_BINARY 0
#endif

#include <stdio.h>

Michael Natterer's avatar
Michael Natterer committed
44
#include "core-types.h"
45

Michael Natterer's avatar
Michael Natterer committed
46 47 48 49
#include "base/base-config.h"
#include "base/brush-scale.h"
#include "base/temp-buf.h"

50
#include "gimpbrush.h"
51
#include "gimpbrush-header.h"
52
#include "gimpbrushgenerated.h"
53

54 55
#include "libgimp/gimpintl.h"

56

Michael Natterer's avatar
Michael Natterer committed
57 58 59 60 61 62 63
enum
{
  SPACING_CHANGED,
  LAST_SIGNAL
};


64 65
static void        gimp_brush_class_init       (GimpBrushClass *klass);
static void        gimp_brush_init             (GimpBrush      *brush);
66 67 68

static void        gimp_brush_finalize         (GObject        *object);

69
static TempBuf   * gimp_brush_get_new_preview  (GimpViewable   *viewable,
70 71
						gint            width,
						gint            height);
72
static gchar     * gimp_brush_get_extension    (GimpData       *data);
73

74
#if 0
75 76
static GimpBrush * gimp_brush_select_brush     (GimpPaintTool  *paint_tool);
static gboolean    gimp_brush_want_null_motion (GimpPaintTool  *paint_tool);
77
#endif
78

79

Michael Natterer's avatar
Michael Natterer committed
80 81 82
static guint brush_signals[LAST_SIGNAL] = { 0 };

static GimpDataClass *parent_class = NULL;
83

84

85
GType
86
gimp_brush_get_type (void)
87
{
88
  static GType brush_type = 0;
89

90
  if (! brush_type)
91
    {
92
      static const GTypeInfo brush_info =
93 94
      {
        sizeof (GimpBrushClass),
95 96 97 98 99 100 101 102
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gimp_brush_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data     */
	sizeof (GimpBrush),
	0,              /* n_preallocs    */
	(GInstanceInitFunc) gimp_brush_init,
103
      };
104

105 106 107
      brush_type = g_type_register_static (GIMP_TYPE_DATA,
					   "GimpBrush", 
					   &brush_info, 0);
108
  }
109 110

  return brush_type;
111 112 113 114 115
}

static void
gimp_brush_class_init (GimpBrushClass *klass)
{
116
  GObjectClass      *object_class;
117
  GimpViewableClass *viewable_class;
118
  GimpDataClass     *data_class;
119

120 121 122
  object_class   = G_OBJECT_CLASS (klass);
  viewable_class = GIMP_VIEWABLE_CLASS (klass);
  data_class     = GIMP_DATA_CLASS (klass);
123

124
  parent_class = g_type_class_peek_parent (klass);
125

Michael Natterer's avatar
Michael Natterer committed
126
  brush_signals[SPACING_CHANGED] = 
127 128 129 130 131 132 133
    g_signal_new ("spacing_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpBrushClass, spacing_changed),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);
Michael Natterer's avatar
Michael Natterer committed
134

135
  object_class->finalize          = gimp_brush_finalize;
136

137
  viewable_class->get_new_preview = gimp_brush_get_new_preview;
138

Michael Natterer's avatar
Michael Natterer committed
139
  data_class->get_extension       = gimp_brush_get_extension;
140

141
#if 0
Michael Natterer's avatar
Michael Natterer committed
142 143
  klass->select_brush             = gimp_brush_select_brush;
  klass->want_null_motion         = gimp_brush_want_null_motion;
144
#endif
145 146
}

147
static void
Michael Natterer's avatar
Michael Natterer committed
148
gimp_brush_init (GimpBrush *brush)
149
{
150 151 152 153 154 155 156 157
  brush->mask     = NULL;
  brush->pixmap   = NULL;

  brush->spacing  = 20;
  brush->x_axis.x = 15.0;
  brush->x_axis.y =  0.0;
  brush->y_axis.x =  0.0;
  brush->y_axis.y = 15.0;
158 159
}

160
static void
161
gimp_brush_finalize (GObject *object)
162 163 164 165
{
  GimpBrush *brush;

  brush = GIMP_BRUSH (object);
166

167
  if (brush->mask)
168 169 170 171
    {
      temp_buf_free (brush->mask);
      brush->mask = NULL;
    }
172

173
  if (brush->pixmap)
174 175 176 177
    {
      temp_buf_free (brush->pixmap);
      brush->pixmap = NULL;
    }
178

179
  G_OBJECT_CLASS (parent_class)->finalize (object);
180 181 182
}

static TempBuf *
183 184 185
gimp_brush_get_new_preview (GimpViewable *viewable,
			    gint          width,
			    gint          height)
186
{
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  GimpBrush   *brush;
  gboolean     scale      = FALSE;
  gint         brush_width;
  gint         brush_height;
  gint         offset_x;
  gint         offset_y;
  TempBuf     *mask_buf   = NULL;
  TempBuf     *pixmap_buf = NULL;
  TempBuf     *return_buf = NULL;
  guchar       white[3] = { 255, 255, 255 };
  guchar      *mask;
  guchar      *buf;
  guchar      *b;
  guchar       bg;
  gint         x, y;
Michael Natterer's avatar
Michael Natterer committed
202

203
  brush = GIMP_BRUSH (viewable);
204

205 206 207 208 209 210 211 212 213 214 215
  mask_buf     = gimp_brush_get_mask (brush);
  pixmap_buf   = gimp_brush_get_pixmap (brush);

  brush_width  = mask_buf->width;
  brush_height = mask_buf->height;

  if (brush_width > width || brush_height > height)
    {
      gdouble ratio_x = (gdouble) brush_width  / width;
      gdouble ratio_y = (gdouble) brush_height / height;

216
      brush_width  = (gdouble) brush_width  / MAX (ratio_x, ratio_y) + 0.5;
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
      brush_height = (gdouble) brush_height / MAX (ratio_x, ratio_y) + 0.5;

      mask_buf = brush_scale_mask (mask_buf, brush_width, brush_height);

      if (pixmap_buf)
        {
          /* TODO: the scale function should scale the pixmap and the
	   *  mask in one run
	   */
          pixmap_buf =
	    brush_scale_pixmap (pixmap_buf, brush_width, brush_height);
        }

      scale = TRUE;
    }

  offset_x = (width  - brush_width)  / 2;
  offset_y = (height - brush_height) / 2;

  return_buf = temp_buf_new (width, height, 3, 0, 0, white);

  mask = temp_buf_data (mask_buf);
  buf  = temp_buf_data (return_buf);

  b = buf + (offset_y * return_buf->width + offset_x) * return_buf->bytes;

  if (pixmap_buf)
    {
      guchar *pixmap = temp_buf_data (pixmap_buf);

      for (y = 0; y < brush_height; y++)
        {
          for (x = 0; x < brush_width ; x++)
            {
              bg = (255 - *mask);

              *b++ = bg + (*mask * *pixmap++) / 255;
254
              *b++ = bg + (*mask * *pixmap++) / 255;
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
              *b++ = bg + (*mask * *pixmap++) / 255;

              mask++;
            }

	  b += (return_buf->width - brush_width) * return_buf->bytes;
        }
    }
  else
    {
      for (y = 0; y < brush_height; y++)
        {
          for (x = 0; x < brush_width ; x++)
            {
              bg = 255 - *mask++;

              *b++ = bg;
              *b++ = bg;
              *b++ = bg;
            }

	  b += (return_buf->width - brush_width) * return_buf->bytes;
        }
    }

  if (scale)
    {
      temp_buf_free (mask_buf);

      if (pixmap_buf)
        temp_buf_free (pixmap_buf);
    }
287

288
  return return_buf;
289 290
}

291 292 293 294 295 296
static gchar *
gimp_brush_get_extension (GimpData *data)
{
  return GIMP_BRUSH_FILE_EXTENSION;
}

297
GimpData *
298 299 300 301 302 303 304 305 306 307
gimp_brush_new (const gchar *name)
{
  GimpBrush *brush;

  g_return_val_if_fail (name != NULL, NULL);

  brush = GIMP_BRUSH (gimp_brush_generated_new (5.0, 0.5, 0.0, 1.0));

  gimp_object_set_name (GIMP_OBJECT (brush), name);

308
  return GIMP_DATA (brush);
309 310
}

311
GimpData *
312 313 314 315 316 317 318 319 320 321 322 323
gimp_brush_get_standard (void)
{
  static GimpBrush *standard_brush = NULL;

  if (! standard_brush)
    {
      standard_brush =
	GIMP_BRUSH (gimp_brush_generated_new (5.0, 0.5, 0.0, 1.0));

      gimp_object_set_name (GIMP_OBJECT (standard_brush), "Standard");

      /*  set ref_count to 2 --> never swap the standard brush  */
324
      g_object_ref (G_OBJECT (standard_brush));
325 326
    }

327
  return GIMP_DATA (standard_brush);
328 329
}

330
GimpData *
331
gimp_brush_load (const gchar *filename)
332
{
333 334
  GimpBrush *brush;
  gint       fd;
Michael Natterer's avatar
Michael Natterer committed
335

336
  g_return_val_if_fail (filename != NULL, NULL);
337

338
  fd = open (filename, O_RDONLY | _O_BINARY);
339
  if (fd == -1)
340 341 342 343 344 345
    return NULL;

  brush = gimp_brush_load_brush (fd, filename);

  close (fd);

346 347 348 349
  if (! brush)
    return NULL;

  gimp_data_set_filename (GIMP_DATA (brush), filename);
350 351

  /*  Swap the brush to disk (if we're being stingy with memory) */
Michael Natterer's avatar
Michael Natterer committed
352
  if (base_config->stingy_memory_use)
353 354
    {
      temp_buf_swap (brush->mask);
355

356 357 358 359
      if (brush->pixmap)
	temp_buf_swap (brush->pixmap);
    }

360
  return GIMP_DATA (brush);
361 362
}

363
#if 0
364
static GimpBrush *
365
gimp_brush_select_brush (GimpPaintTool *paint_core)
366 367 368 369
{
  return paint_core->brush;
}

370
static gboolean
371
gimp_brush_want_null_motion (GimpPaintTool *paint_core)
372 373 374
{
  return TRUE;
}
375
#endif
376

377
TempBuf *
378
gimp_brush_get_mask (const GimpBrush *brush)
379
{
380
  g_return_val_if_fail (brush != NULL, NULL);
Michael Natterer's avatar
Michael Natterer committed
381 382
  g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);

383 384 385
  return brush->mask;
}

386
TempBuf *
387
gimp_brush_get_pixmap (const GimpBrush *brush)
388 389 390 391 392 393 394
{
  g_return_val_if_fail (brush != NULL, NULL);
  g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);

  return brush->pixmap;
}

Michael Natterer's avatar
Michael Natterer committed
395
gint
396
gimp_brush_get_spacing (const GimpBrush *brush)
397 398 399 400 401 402 403 404
{
  g_return_val_if_fail (GIMP_IS_BRUSH (brush), 0);

  return brush->spacing;
}

void
gimp_brush_set_spacing (GimpBrush *brush,
Michael Natterer's avatar
Michael Natterer committed
405
			gint       spacing)
406 407 408
{
  g_return_if_fail (GIMP_IS_BRUSH (brush));

Michael Natterer's avatar
Michael Natterer committed
409 410 411 412 413 414 415 416 417 418 419 420 421
  if (brush->spacing != spacing)
    {
      brush->spacing = spacing;

      gimp_brush_spacing_changed (brush);
    }
}

void
gimp_brush_spacing_changed (GimpBrush *brush)
{
  g_return_if_fail (GIMP_IS_BRUSH (brush));

422
  g_signal_emit (G_OBJECT (brush), brush_signals[SPACING_CHANGED], 0);
423 424
}

425
GimpBrush *
426 427
gimp_brush_load_brush (gint         fd,
		       const gchar *filename)
428
{
429 430 431
  GimpBrush   *brush;
  gint         bn_size;
  BrushHeader  header;
432
  gchar       *name = NULL;
433
  gint         i;
434

435 436
  g_return_val_if_fail (filename != NULL, NULL);
  g_return_val_if_fail (fd != -1, NULL);
437

438
  /*  Read in the header size  */
439
  if (read (fd, &header, sizeof (header)) != sizeof (header))
440
    return NULL;
441 442

  /*  rearrange the bytes in each unsigned int  */
443 444 445 446 447 448 449
  header.header_size  = g_ntohl (header.header_size);
  header.version      = g_ntohl (header.version);
  header.width        = g_ntohl (header.width);
  header.height       = g_ntohl (header.height);
  header.bytes        = g_ntohl (header.bytes);
  header.magic_number = g_ntohl (header.magic_number);
  header.spacing      = g_ntohl (header.spacing);
450 451

  /*  Check for correct file format */
452 453 454
  /*  It looks as if version 1 did not have the same magic number.  (neo)  */
  if (header.version != 1 &&
      (header.magic_number != GBRUSH_MAGIC || header.version != 2))
455
    {
456 457
      g_message (_("Fatal parsing error (unknown version %d):\n"
		   "Brush file '%s'"),
458 459
		 header.version, filename);
      return NULL;
460 461 462
    }

  if (header.version == 1)
463 464
    {
      /*  If this is a version 1 brush, set the fp back 8 bytes  */
465
      lseek (fd, -8, SEEK_CUR);
466 467 468 469 470 471
      header.header_size += 8;
      /*  spacing is not defined in version 1  */
      header.spacing = 25;
    }
  
   /*  Read in the brush name  */
472
  if ((bn_size = (header.header_size - sizeof (header))))
473
    {
474 475
      name = g_new (gchar, bn_size);
      if ((read (fd, name, bn_size)) < bn_size)
476
	{
477 478
	  g_message (_("Fatal parsing error:\nBrush file '%s' appears truncated."),
		     filename);
479 480
	  g_free (name);
	  return NULL;
481
	}
482 483 484

      if (!g_utf8_validate (name, -1, NULL))
        {
485
          g_message (_("Invalid UTF-8 string in brush file '%s'."), 
486 487 488 489
                     filename);
          g_free (name);
          name = NULL;
        }
490
    }
491 492 493
  
  if (!name)
    name = g_strdup (_("Unnamed"));
Michael Natterer's avatar
Michael Natterer committed
494

495
  switch (header.bytes)
496
    {
Michael Natterer's avatar
Michael Natterer committed
497
    case 1:
Michael Natterer's avatar
Michael Natterer committed
498
      brush = GIMP_BRUSH (g_object_new (GIMP_TYPE_BRUSH, NULL));
499
      brush->mask = temp_buf_new (header.width, header.height, 1,
Michael Natterer's avatar
Michael Natterer committed
500
				  0, 0, NULL);
501
      if (read (fd,
502
		temp_buf_data (brush->mask), header.width * header.height) <
503
	  header.width * header.height)
504
	{
505
	  g_message (_("Fatal parsing error:\nBrush file '%s' appears truncated."),
506 507
		     filename);
	  g_free (name);
508
	  g_object_unref (G_OBJECT (brush));
509 510
	  return NULL;
	}
Michael Natterer's avatar
Michael Natterer committed
511
      break;
512

513
    case 4:
Michael Natterer's avatar
Michael Natterer committed
514
      brush = GIMP_BRUSH (g_object_new (GIMP_TYPE_BRUSH, NULL));
515 516 517 518 519
      brush->mask =   temp_buf_new (header.width, header.height, 1, 0, 0, NULL);
      brush->pixmap = temp_buf_new (header.width, header.height, 3, 0, 0, NULL);

      for (i = 0; i < header.width * header.height; i++)
	{
520
	  if (read (fd, temp_buf_data (brush->pixmap)
521 522 523
		    + i * 3, 3) != 3 ||
	      read (fd, temp_buf_data (brush->mask) + i, 1) != 1)
	    {
524
	      g_message (_("Fatal parsing error:\nBrush file '%s' appears truncated."),
525 526
			 filename);
	      g_free (name);
527
	      g_object_unref (G_OBJECT (brush));
528 529 530 531
	      return NULL;
	    }
	}
      break;
532

Michael Natterer's avatar
Michael Natterer committed
533
    default:
534 535 536
      g_message ("Unsupported brush depth %d\n"
		 "in file '%s'.\n"
		 "GIMP brushes must be GRAY or RGBA.",
537 538 539
		 header.bytes, filename);
      g_free (name);
      return NULL;
540
    }
541

542 543 544 545
  gimp_object_set_name (GIMP_OBJECT (brush), name);

  g_free (name);

546 547 548 549 550 551 552
  brush->spacing  = header.spacing;
  brush->x_axis.x = header.width  / 2.0;
  brush->x_axis.y = 0.0;
  brush->y_axis.x = 0.0;
  brush->y_axis.y = header.height / 2.0;

  return brush;
553
}
554 555 556 557