void
gimp_image_convert_precision (GimpImage     *image,
                              GimpPrecision  precision,
                              gint           layer_dither_type,
                              gint           text_layer_dither_type,
                              gint           mask_dither_type,
                              GimpProgress  *progress)
{
  GimpColorProfile *old_profile;
  GimpColorProfile *new_profile = NULL;
  const Babl       *old_format;
  const Babl       *new_format;
  GList            *all_drawables;
  GList            *list;
  const gchar      *undo_desc    = NULL;
  GimpProgress     *sub_progress = NULL;
  gint              nth_drawable, n_drawables;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (precision != gimp_image_get_precision (image));
  g_return_if_fail (precision == GIMP_PRECISION_U8_GAMMA ||
                    gimp_image_get_base_type (image) != GIMP_INDEXED);
  g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));

  all_drawables = g_list_concat (gimp_image_get_layer_list (image),
                                 gimp_image_get_channel_list (image));

  n_drawables = g_list_length (all_drawables) + 1 /* + selection */;

  if (progress)
    sub_progress = gimp_sub_progress_new (progress);

  switch (precision)
    {
    case GIMP_PRECISION_U8_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 8 bit linear integer");
      break;
    case GIMP_PRECISION_U8_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 8 bit gamma integer");
      break;
    case GIMP_PRECISION_U16_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 16 bit linear integer");
      break;
    case GIMP_PRECISION_U16_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 16 bit gamma integer");
      break;
    case GIMP_PRECISION_U32_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 32 bit linear integer");
      break;
    case GIMP_PRECISION_U32_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 32 bit gamma integer");
      break;
    case GIMP_PRECISION_HALF_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 16 bit linear floating point");
      break;
    case GIMP_PRECISION_HALF_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 16 bit gamma floating point");
      break;
    case GIMP_PRECISION_FLOAT_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 32 bit linear floating point");
      break;
    case GIMP_PRECISION_FLOAT_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 32 bit gamma floating point");
      break;
    case GIMP_PRECISION_DOUBLE_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 64 bit linear floating point");
      break;
    case GIMP_PRECISION_DOUBLE_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 64 bit gamma floating point");
      break;
    }

  if (progress)
    gimp_progress_start (progress, FALSE, "%s", undo_desc);

  g_object_freeze_notify (G_OBJECT (image));

  gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
                               undo_desc);

  /*  Push the image precision to the stack  */
  gimp_image_undo_push_image_precision (image, NULL);

  old_profile = gimp_image_get_color_profile (image);
  old_format  = gimp_image_get_layer_format (image, FALSE);

  /*  Set the new precision  */
  g_object_set (image, "precision", precision, NULL);

  new_format = gimp_image_get_layer_format (image, FALSE);

  if (old_profile &&
      gimp_babl_format_get_linear (old_format) !=
      gimp_babl_format_get_linear (new_format))
    {
      GimpColorProfile *new_profile;

      /* when converting between linear and gamma, we create a new
       * profile using the original profile's chromacities and
       * whitepoint, but a linear/sRGB-gamma TRC.
       */

      if (gimp_babl_format_get_linear (new_format))
        {
          new_profile =
            gimp_color_profile_new_linear_from_color_profile (old_profile);
        }
      else
        {
          new_profile =
            gimp_color_profile_new_srgb_trc_from_color_profile (old_profile);
        }

      /* if a new profile cannot be be generated, convert to the
       * builtin profile, which is better than leaving the user with
       * broken colors
       */
      if (! new_profile)
        {
          new_profile = gimp_image_get_builtin_color_profile (image);
          g_object_ref (new_profile);
        }
    }

  for (list = all_drawables, nth_drawable = 0;
       list;
       list = g_list_next (list), nth_drawable++)
    {
      GimpDrawable *drawable = list->data;
      gint          dither_type;

      if (gimp_item_is_text_layer (GIMP_ITEM (drawable)))
        dither_type = text_layer_dither_type;
      else
        dither_type = layer_dither_type;

      if (sub_progress)
        gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                    nth_drawable, n_drawables);

      gimp_drawable_convert_type (drawable, image,
                                  gimp_drawable_get_base_type (drawable),
                                  precision,
                                  new_profile,
                                  dither_type,
                                  mask_dither_type,
                                  TRUE, sub_progress);
    }

  g_list_free (all_drawables);

  if (old_profile)
    {
      gimp_image_set_color_profile (image, new_profile, NULL);
      g_object_unref (new_profile);
    }

  /*  convert the selection mask  */
  {
    GimpChannel *mask = gimp_image_get_mask (image);
    GeglBuffer  *buffer;

    if (sub_progress)
      gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                  nth_drawable, n_drawables);

    gimp_image_undo_push_mask_precision (image, NULL, mask);

    buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                              gimp_image_get_width  (image),
                                              gimp_image_get_height (image)),
                              gimp_image_get_mask_format (image));

    gegl_buffer_copy (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)), NULL,
                      GEGL_ABYSS_NONE,
                      buffer, NULL);

    gimp_drawable_set_buffer (GIMP_DRAWABLE (mask), FALSE, NULL, buffer);
    g_object_unref (buffer);

    nth_drawable++;
  }

  if (sub_progress)
    gimp_progress_set_value (sub_progress, 1.0);

  gimp_image_undo_group_end (image);

  gimp_image_precision_changed (image);
  g_object_thaw_notify (G_OBJECT (image));

  if (sub_progress)
    g_object_unref (sub_progress);

  if (progress)
    gimp_progress_end (progress);
}
void
gimp_image_convert_precision (GimpImage     *image,
                              GimpPrecision  precision,
                              gint           layer_dither_type,
                              gint           text_layer_dither_type,
                              gint           mask_dither_type,
                              GimpProgress  *progress)
{
  GList       *all_drawables;
  GList       *list;
  const gchar *undo_desc = NULL;
  gint         nth_drawable, n_drawables;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (precision != gimp_image_get_precision (image));
  g_return_if_fail (precision == GIMP_PRECISION_U8_GAMMA ||
                    gimp_image_get_base_type (image) != GIMP_INDEXED);
  g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));

  all_drawables = g_list_concat (gimp_image_get_layer_list (image),
                                 gimp_image_get_channel_list (image));

  n_drawables = g_list_length (all_drawables) + 1 /* + selection */;

  switch (precision)
    {
    case GIMP_PRECISION_U8_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 8 bit linear integer");
      break;
    case GIMP_PRECISION_U8_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 8 bit gamma integer");
      break;
    case GIMP_PRECISION_U16_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 16 bit linear integer");
      break;
    case GIMP_PRECISION_U16_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 16 bit gamma integer");
      break;
    case GIMP_PRECISION_U32_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 32 bit linear integer");
      break;
    case GIMP_PRECISION_U32_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 32 bit gamma integer");
      break;
    case GIMP_PRECISION_HALF_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 16 bit linear floating point");
      break;
    case GIMP_PRECISION_HALF_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 16 bit gamma floating point");
      break;
    case GIMP_PRECISION_FLOAT_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 32 bit linear floating point");
      break;
    case GIMP_PRECISION_FLOAT_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 32 bit gamma floating point");
      break;
    case GIMP_PRECISION_DOUBLE_LINEAR:
      undo_desc = C_("undo-type", "Convert Image to 64 bit linear floating point");
      break;
    case GIMP_PRECISION_DOUBLE_GAMMA:
      undo_desc = C_("undo-type", "Convert Image to 64 bit gamma floating point");
      break;
    }

  if (progress)
    gimp_progress_start (progress, FALSE, "%s", undo_desc);

  g_object_freeze_notify (G_OBJECT (image));

  gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
                               undo_desc);

  /*  Push the image precision to the stack  */
  gimp_image_undo_push_image_precision (image, NULL);

  /*  Set the new precision  */
  g_object_set (image, "precision", precision, NULL);

  for (list = all_drawables, nth_drawable = 0;
       list;
       list = g_list_next (list), nth_drawable++)
    {
      GimpDrawable *drawable = list->data;
      gint          dither_type;

      if (gimp_item_is_text_layer (GIMP_ITEM (drawable)))
        dither_type = text_layer_dither_type;
      else
        dither_type = layer_dither_type;

      gimp_drawable_convert_type (drawable, image,
                                  gimp_drawable_get_base_type (drawable),
                                  precision,
                                  dither_type,
                                  mask_dither_type,
                                  FALSE,
                                  TRUE);

      if (progress)
        gimp_progress_set_value (progress,
                                 (gdouble) nth_drawable / (gdouble) n_drawables);
    }
  g_list_free (all_drawables);

  /*  convert the selection mask  */
  {
    GimpChannel *mask = gimp_image_get_mask (image);
    GeglBuffer  *buffer;

    gimp_image_undo_push_mask_precision (image, NULL, mask);

    buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                              gimp_image_get_width  (image),
                                              gimp_image_get_height (image)),
                              gimp_image_get_mask_format (image));

    gegl_buffer_copy (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)), NULL,
                      GEGL_ABYSS_NONE,
                      buffer, NULL);

    gimp_drawable_set_buffer (GIMP_DRAWABLE (mask), FALSE, NULL, buffer);
    g_object_unref (buffer);

    nth_drawable++;

    if (progress)
      gimp_progress_set_value (progress,
                               (gdouble) nth_drawable / (gdouble) n_drawables);
  }

  gimp_image_undo_group_end (image);

  gimp_image_precision_changed (image);
  g_object_thaw_notify (G_OBJECT (image));

  if (progress)
    gimp_progress_end (progress);
}
void
gimp_image_scale (GimpImage             *image,
                  gint                   new_width,
                  gint                   new_height,
                  GimpInterpolationType  interpolation_type,
                  GimpProgress          *progress)
{
    GimpProgress *sub_progress;
    GList        *all_layers;
    GList        *all_channels;
    GList        *all_vectors;
    GList        *list;
    gint          old_width;
    gint          old_height;
    gint          offset_x;
    gint          offset_y;
    gdouble       img_scale_w      = 1.0;
    gdouble       img_scale_h      = 1.0;
    gint          progress_steps;
    gint          progress_current = 0;

    g_return_if_fail (GIMP_IS_IMAGE (image));
    g_return_if_fail (new_width > 0 && new_height > 0);
    g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));

    gimp_set_busy (image->gimp);

    sub_progress = gimp_sub_progress_new (progress);

    all_layers   = gimp_image_get_layer_list (image);
    all_channels = gimp_image_get_channel_list (image);
    all_vectors  = gimp_image_get_vectors_list (image);

    progress_steps = (g_list_length (all_layers)   +
                      g_list_length (all_channels) +
                      g_list_length (all_vectors)  +
                      1 /* selection */);

    g_object_freeze_notify (G_OBJECT (image));

    gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_SCALE,
                                 C_("undo-type", "Scale Image"));

    old_width   = gimp_image_get_width  (image);
    old_height  = gimp_image_get_height (image);
    img_scale_w = (gdouble) new_width  / (gdouble) old_width;
    img_scale_h = (gdouble) new_height / (gdouble) old_height;

    offset_x = (old_width  - new_width)  / 2;
    offset_y = (old_height - new_height) / 2;

    /*  Push the image size to the stack  */
    gimp_image_undo_push_image_size (image,
                                     NULL,
                                     offset_x,
                                     offset_y,
                                     new_width,
                                     new_height);

    /*  Set the new width and height early, so below image item setters
     *  (esp. guides and sample points) don't choke about moving stuff
     *  out of the image
     */
    g_object_set (image,
                  "width",  new_width,
                  "height", new_height,
                  NULL);

    /*  Scale all channels  */
    for (list = all_channels; list; list = g_list_next (list))
    {
        GimpItem *item = list->data;

        gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                    progress_current++, progress_steps);

        gimp_item_scale (item,
                         new_width, new_height, 0, 0,
                         interpolation_type, sub_progress);
    }

    /*  Scale all vectors  */
    for (list = all_vectors; list; list = g_list_next (list))
    {
        GimpItem *item = list->data;

        gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                    progress_current++, progress_steps);

        gimp_item_scale (item,
                         new_width, new_height, 0, 0,
                         interpolation_type, sub_progress);
    }

    /*  Don't forget the selection mask!  */
    gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                progress_current++, progress_steps);

    gimp_item_scale (GIMP_ITEM (gimp_image_get_mask (image)),
                     new_width, new_height, 0, 0,
                     interpolation_type, sub_progress);

    /*  Scale all layers  */
    for (list = all_layers; list; list = g_list_next (list))
    {
        GimpItem *item = list->data;

        gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (sub_progress),
                                    progress_current++, progress_steps);

        /*  group layers are updated automatically  */
        if (gimp_viewable_get_children (GIMP_VIEWABLE (item)))
        {
            gimp_group_layer_suspend_resize (GIMP_GROUP_LAYER (item), FALSE);
            continue;
        }

        if (! gimp_item_scale_by_factors (item,
                                          img_scale_w, img_scale_h,
                                          interpolation_type, sub_progress))
        {
            /* Since 0 < img_scale_w, img_scale_h, failure due to one or more
             * vanishing scaled layer dimensions. Implicit delete implemented
             * here. Upstream warning implemented in resize_check_layer_scaling(),
             * which offers the user the chance to bail out.
             */
            gimp_image_remove_layer (image, GIMP_LAYER (item), TRUE, NULL);
        }
    }

    for (list = all_layers; list; list = g_list_next (list))
        if (gimp_viewable_get_children (list->data))
            gimp_group_layer_resume_resize (list->data, FALSE);


    /*  Scale all Guides  */
    for (list = gimp_image_get_guides (image);
            list;
            list = g_list_next (list))
    {
        GimpGuide *guide    = list->data;
        gint       position = gimp_guide_get_position (guide);

        switch (gimp_guide_get_orientation (guide))
        {
        case GIMP_ORIENTATION_HORIZONTAL:
            gimp_image_move_guide (image, guide,
                                   (position * new_height) / old_height,
                                   TRUE);
            break;

        case GIMP_ORIENTATION_VERTICAL:
            gimp_image_move_guide (image, guide,
                                   (position * new_width) / old_width,
                                   TRUE);
            break;

        default:
            break;
        }
    }

    /*  Scale all sample points  */
    for (list = gimp_image_get_sample_points (image);
            list;
            list = g_list_next (list))
    {
        GimpSamplePoint *sample_point = list->data;

        gimp_image_move_sample_point (image, sample_point,
                                      sample_point->x * new_width  / old_width,
                                      sample_point->y * new_height / old_height,
                                      TRUE);
    }

    gimp_image_undo_group_end (image);

    g_list_free (all_layers);
    g_list_free (all_channels);
    g_list_free (all_vectors);

    g_object_unref (sub_progress);

    gimp_image_size_changed_detailed (image,
                                      -offset_x,
                                      -offset_y,
                                      old_width,
                                      old_height);

    g_object_thaw_notify (G_OBJECT (image));

    gimp_unset_busy (image->gimp);
}