Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add an option for strip metadata but without removing icc profile? #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
aligo opened this issue Jul 4, 2018 · 20 comments
Closed

Add an option for strip metadata but without removing icc profile? #1026

aligo opened this issue Jul 4, 2018 · 20 comments
Labels

Comments

@aligo
Copy link

aligo commented Jul 4, 2018

No description provided.

@jcupitt
Copy link
Member

jcupitt commented Jul 4, 2018

Could you explain the use case?

If you want to make very small thumbnail files, I would transform to sRGB and not attach a profile.

If you want to remove most metadata but leave the profile, I would just remove individual tags. For example:

john@kiwi:~/pics$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyvips
>>> y = pyvips.Image.new_from_file("k2.jpg")
>>> x = y.copy()
>>> x.remove("iptc-data")
True
>>> x.remove("jpeg-thumbnail-data")
True

etc.

@aligo
Copy link
Author

aligo commented Jul 4, 2018

thx @jcupitt

In my case I want to make small thumbnails, but being used to display on iPhone, I want to keep the original DCI-P3 icc profile rather than transforming to sRGB for better screen performance.

Removing other metadata and keeping icc should be able to handle it, I will do that at this moment.

The option I want maybe is not necessary.

@jcupitt
Copy link
Member

jcupitt commented Jul 4, 2018

Ah, OK, that makes sense.

You can remove all fields like this:

john@kiwi:~/pics$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyvips
>>> y = pyvips.Image.new_from_file("k2.jpg")
>>> x = y.copy()
>>> x.get_fields()
['width', 'height', 'bands', 'format', 'coding', 'interpretation', 'xoffset', 'yoffset', 'xres', 'yres', 'filename', 'vips-loader', 'jpeg-multiscan', 'jpeg-chroma-subsample', 'exif-data', 'resolution-unit', 'exif-ifd0-Orientation', 'exif-ifd0-XResolution', 'exif-ifd0-YResolution', 'exif-ifd0-ResolutionUnit', 'exif-ifd2-ExifVersion', 'exif-ifd2-FlashPixVersion', 'exif-ifd2-ColorSpace', 'exif-ifd2-PixelXDimension', 'exif-ifd2-PixelYDimension', 'orientation']
>>> for name in x.get_fields():
...     x.remove(name)
... 
>>> x.get_fields()
['width', 'height', 'bands', 'format', 'coding', 'interpretation', 'xoffset', 'yoffset', 'xres', 'yres', 'filename']

And then reattach a profile.

@aligo aligo closed this as completed Jul 4, 2018
@nullne
Copy link

nullne commented Jun 30, 2020

@jcupitt i met the same problem. but how to implement in c code ?

@jcupitt
Copy link
Member

jcupitt commented Jun 30, 2020

The C API is documented here:

https://libvips.github.io/libvips/API/current/libvips-header.html

It's very close to Python.

@nullne
Copy link

nullne commented Jun 30, 2020

thank you in advance @jcupitt
but the python demo above won't work, i paste the code here :

In [49]: x = pyvips.Image.new_from_file("/tmp/image/b.jpeg")

In [50]: for name in x.get_fields():
    ...:     if name == 'icc-profile-data':
    ...:         continue
    ...:     x.remove(name)
    ...:

In [51]: x.get_fields()
Out[51]:
['width',
 'height',
 'bands',
 'format',
 'coding',
 'interpretation',
 'xoffset',
 'yoffset',
 'xres',
 'yres',
 'filename']

In [52]: x.write_to_file("/tmp/image/m.jpeg")

In [53]: y = pyvips.Image.new_from_file("/tmp/image/m.jpeg")

In [54]: y.get_fields()
Out[54]:
['width',
 'height',
 'bands',
 'format',
 'coding',
 'interpretation',
 'xoffset',
 'yoffset',
 'xres',
 'yres',
 'filename',
 'vips-loader',
 'jpeg-multiscan',
 'jpeg-chroma-subsample',
 'exif-data',
 'resolution-unit',
 'exif-ifd0-Orientation',
 'exif-ifd0-XResolution',
 'exif-ifd0-YResolution',
 'exif-ifd0-ResolutionUnit',
 'exif-ifd0-YCbCrPositioning',
 'exif-ifd2-ExifVersion',
 'exif-ifd2-ComponentsConfiguration',
 'exif-ifd2-FlashPixVersion',
 'exif-ifd2-ColorSpace',
 'exif-ifd2-PixelXDimension',
 'exif-ifd2-PixelYDimension',
 'orientation',
 'icc-profile-data']

@jcupitt
Copy link
Member

jcupitt commented Jun 30, 2020

There's been a change to metadata handling in libvips 8.9 -- you now need to copy() before you can remove metadata. This ensures you have a private reference to an image before you modify it and stops some nasty race conditions in heavily threaded applications.

Try:

>>> x = pyvips.Image.new_from_file("pics/k2.jpg")
>>> y = x.copy()
>>> for name in y.get_fields(): 
...     y.remove(name)
...
>>> y.get_fields()
['width', 'height', 'bands', 'format', 'coding', 'interpretation', 'xoffset', 'yoffset', 'xres', 'yres', 'filename']

@jcupitt
Copy link
Member

jcupitt commented Jun 30, 2020

I'll update that earlier answer, thanks for pointing this out.

@nullne
Copy link

nullne commented Jun 30, 2020

There's been a change to metadata handling in libvips 8.9 -- you now need to copy() before you can remove metadata. This ensures you have a private reference to an image before you modify it and stops some nasty race conditions in heavily threaded applications.

Try:

>>> x = pyvips.Image.new_from_file("pics/k2.jpg")
>>> y = x.copy()
>>> for name in y.get_fields(): 
...     y.remove(name)
...
>>> y.get_fields()
['width', 'height', 'bands', 'format', 'coding', 'interpretation', 'xoffset', 'yoffset', 'xres', 'yres', 'filename']

should be x.copy_memory instead of x.copy

@nullne
Copy link

nullne commented Jun 30, 2020

@jcupitt and is it necessary to copy in c API? i didn't find any copy method from the link above

@jcupitt
Copy link
Member

jcupitt commented Jun 30, 2020

Don't use .copy_memory() -- that allocates a huge area of memory and renders the entire image into it.

Plain .copy() just copies the pointer and makes sure you have a unique version of the metadata. It's very quick and it's all you need for thread safety.

In C, it's vips_copy():

https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-copy

@nullne
Copy link

nullne commented Jul 1, 2020

@jcupitt why won't c implementation remove fields? it works in python. could you please have a look at it, thanks very much

#include <stdio.h>
#include <vips/vips.h>

int
main( int argc, char **argv )
{
  VipsImage *in;
  /* double mean; */
  VipsImage *out;

  if( VIPS_INIT( argv[0] ) )
    vips_error_exit( NULL );

  if( argc != 3 )
    vips_error_exit( "usage: %s infile outfile", argv[0] );

  if( !(in = vips_image_new_from_file( argv[1], NULL )) )
    vips_error_exit( NULL );


  vips_copy(in, &out, NULL);

  gchar** fields;
  fields = vips_image_get_fields( out );
  gchar* tmp;
  while((tmp = *fields)!=NULL) {
      if ( strcmp(tmp, "icc-profile-data" ) != 0) {
          printf("%s\n", *fields);
          vips_image_remove(out, tmp);
      }
      fields++;
  }

  if( vips_image_write_to_file( out, argv[2], NULL ) )
    vips_error_exit( NULL );

  g_object_unref( out );

  return( 0 );
}

@jcupitt
Copy link
Member

jcupitt commented Jul 1, 2020

It is working I think. The jpeg writer always attaches some EXIF, since the spec requires it.

I see:

$ vipsheader -a ~/pics/rot.jpg | grep exif-data
exif-data: 1338 bytes of binary data
$ ./a.out ~/pics/rot.jpg x.jpg
...
$ vipsheader -a x.jpg | grep exif-data
exif-data: 186 bytes of binary data

@jcupitt
Copy link
Member

jcupitt commented Jul 1, 2020

Also, you need to free the result of vips_image_get_fields() or you'll have a leak.

It's really there for bindings. For C, I would use vips_header_map() instead, eg.:

/* compile with:
 *
 * gcc -g -Wall remove-fields.c `pkg-config vips --cflags --libs`
 *
 */

#include <stdio.h>
#include <vips/vips.h>

static void *
remove_fields( VipsImage *image, const char *name, GValue *value, void *a )
{
  if ( strcmp(name, "icc-profile-data" ) != 0) {
    printf("%s\n", name);
    vips_image_remove(image, name);
  }
  
  return( NULL );
}

int
main( int argc, char **argv )
{
  VipsImage *image;
  VipsImage *x;

  if( VIPS_INIT( argv[0] ) )
    vips_error_exit( NULL );

  if( argc != 3 )
    vips_error_exit( "usage: %s infile outfile", argv[0] );

  if( !(image = vips_image_new_from_file( argv[1], NULL )) )
    vips_error_exit( NULL );

  if( vips_copy( image, &x, NULL ) )
    vips_error_exit( NULL );
  g_object_unref( image );
  image = x;

  vips_image_map( image, remove_fields, NULL );

  if( vips_image_write_to_file( image, argv[2], NULL ) )
    vips_error_exit( NULL );

  g_object_unref( image );

  return( 0 );
}

Now there's nothing to free, races are impossible and you don't need to write a loop.

@nullne
Copy link

nullne commented Jul 2, 2020

It is working I think. The jpeg writer always attaches some EXIF, since the spec requires it.

I see:

$ vipsheader -a ~/pics/rot.jpg | grep exif-data
exif-data: 1338 bytes of binary data
$ ./a.out ~/pics/rot.jpg x.jpg
...
$ vipsheader -a x.jpg | grep exif-data
exif-data: 186 bytes of binary data

this may explain why there are some meta won't be deleted:

m.jpeg: 1512x1512 uchar, 3 bands, srgb, jpegload
width: 1512
height: 1512
bands: 3
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 2.83465
yres: 2.83465
filename: m.jpeg
vips-loader: jpegload
jpeg-multiscan: 0
jpeg-chroma-subsample: 4:2:0
exif-data: 186 bytes of binary data
resolution-unit: in
exif-ifd0-Orientation: 1 (Top-left, Short, 1 components, 2 bytes)
exif-ifd0-XResolution: 72/1 (72, Rational, 1 components, 8 bytes)
exif-ifd0-YResolution: 72/1 (72, Rational, 1 components, 8 bytes)
exif-ifd0-ResolutionUnit: 2 (Inch, Short, 1 components, 2 bytes)
exif-ifd0-YCbCrPositioning: 1 (Centered, Short, 1 components, 2 bytes)
exif-ifd2-ExifVersion: Exif Version 2.1 (Exif Version 2.1, Undefined, 4 components, 4 bytes)
exif-ifd2-ComponentsConfiguration: Y Cb Cr - (Y Cb Cr -, Undefined, 4 components, 4 bytes)
exif-ifd2-FlashPixVersion: FlashPix Version 1.0 (FlashPix Version 1.0, Undefined, 4 components, 4 bytes)
exif-ifd2-ColorSpace: 65535 (Uncalibrated, Short, 1 components, 2 bytes)
exif-ifd2-PixelXDimension: 1512 (1512, Long, 1 components, 4 bytes)
exif-ifd2-PixelYDimension: 1512 (1512, Long, 1 components, 4 bytes)
orientation: 1
icc-profile-data: 536 bytes of binary data

i will strip all metadata when saving images like this:

vips_jpegsave_buffer(in, buf, len,
       "strip", INT_TO_GBOOLEAN(strip),
       "Q", quality,
       "optimize_coding", TRUE,
       "interlace", INT_TO_GBOOLEAN(interlace),
       NULL
   );

in which strip is set to true.
so how can i remove all the meta except icc-profile-data? @jcupitt thank you for your time as always

@jcupitt
Copy link
Member

jcupitt commented Jul 2, 2020

That EXIF block is only 186 bytes, and it is required by the JPEG specification. I would not remove it.

@L3tum
Copy link

L3tum commented Nov 17, 2021

@jcupitt Sorry to revive such an old topic, but I've found no way to do the same thing in PHP. As far as I can tell, vips_image_get_fields is not exposed and trying to call it myself via Image::call results in the error VipsOperation: class image_get_fields not found (or vips_image_get_fields, or simply get_fields).

@jcupitt
Copy link
Member

jcupitt commented Nov 17, 2021

Sorry, php-vips is missing get_fields (or getFields I suppose it would be). Someone needs to add it to php-vips-ext, then add a wrapper to php-vips.

Back when we designed php-vips-ext there was no good FFI package for php, so we had to do it in plain C. There does seem to be a reasonable FFI available now, so a better fix would be to get rid of php-vips-ext and just do everything in php-vips. It would make maintenance and installation much simpler.

Like everything, it's a question of finding the time. If anyone would like to volunteer ...

@L3tum
Copy link

L3tum commented Nov 17, 2021

Thanks, I can see if I can add it to the extension (although I don't know if we personally can update^^). I can't promise anything as of now though.

I'd avoid PHP FFI at least for now, it seems to be worse for performance since it needs to parse the definitions and load the symbols instead of having it compiled ahead of time with the extension. It would be a good idea in the long run though, building & installing the extension was a pain to set up for us (though mostly because of PHP Docker Images being stuck on an old version of Debian for so long).

@jcupitt
Copy link
Member

jcupitt commented Nov 17, 2021

I agree, there would be a performance hit from using php-ffi, but I think only a small one. The improvement in portability, maintenance and ease of installation would be worth it (imo).

There are some benchmarks here:

https://github.com/libvips/libvips/wiki/Speed-and-memory-use

ruby-vips has a fully interpreted FFI binding and runs the benchmark in 400ms, php-vips has a compiled binding and takes 350ms, so you might expect a FFI php-vips to be of the order of 10% slower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants