strukturag / libheif

libheif is an HEIF and AVIF file format decoder and encoder.
Other
1.64k stars 294 forks source link

Saving in RGB colorspace (was: "heif-enc lossless isn't lossless") #62

Closed kliberty closed 1 month ago

kliberty commented 5 years ago

I used heif-enc to convert a png "losslessly" then converted it back to png with heif-convert and the results where not lossless. I attached the original png bellow as well as the difference between them (levels increased dramatically).

heif-enc -L tree.png
heif-convert tree.heic tree.png

ash_tree_-_geograph org uk_-_590710 png-heic-png

farindk commented 5 years ago

"Lossless" refers to the HEVC compression settings. When you are encoding from a PNG, there is a color-space conversion involved. PNGs are stored in RGB color-space, while HEIF is in YUV. Hence, you get some loss because of the color-space conversion.

TODO: 'heif-enc' should print a warning about this.

kliberty commented 5 years ago

Ah, thank you. x265 has an RGB mode, though I can't figure out what parameter enables it. cwebp switches to ARGB when it's lossless mode is enabled...that might be worth considering.

farindk commented 5 years ago

Yes, this is definitely the way to go. However, I am still unsure how to correctly indicate the colour space of the image data. Strangely, I didn't see anything about this in the HEIF and HEVC standards, and I also did not come across any RGB coded HEIF images so far. Maybe the coding of RGB images was overlooked?

farindk commented 5 years ago

A warning is now printed when an RGB input image is compressed to YUV in lossless mode (6bd1a7e).

farindk commented 5 years ago

I'm still trying to figure out how RGB images might be stored in an HEIF image. Now, I found that one could set the matrix_coefficients in the color profile to identity, corresponding to no RGB/YUV conversion. However, as is described in ISO 14496-12, the colr box is only advisory and not mandatory to be interpreted by a reader.

Any hints about this are welcome. If RGB colorspace is really something that has been overlooked by the HEIF standard, we might have to define it.

dbtsai commented 5 years ago

Out of my curiosity, does iOS/OSX render colr box?

I know JPEG is using YUV internally, but in ImageMagick, JPEG will aways render as RGB. Do you know why it works like this?

On the plus side, it gives the right mae metric when I use compare to compare the original png file.

BTW, does ImageMagick with heif support lossless mode when quality = 100? When jpeg200 with quality = 100, it will be switched into lossless mode.

kliberty commented 5 years ago

My impression was that RGB support in HEIF is implicit. HEIF supports all formats of HEVC, and HEVC supports RGB. The documentation I saw referred to it as GBR, I think the technique is to store Y=G, Cb=B, and Cr=R, though I'm not sure how it is indicated in the container that the YCbCr data is actually GBR. I also saw that you're storing the YUV with 4:2:0 sub-sampling and I don't think that will turn out well with GBR. Not to be picky, but might be worth adding that information about sub-sampling to the warning (or adding a switch for 444) since sub-sampling will affect text clarity.

This article might help

kliberty commented 5 years ago

@dbtsai Sorry, but what do you mean "will always render as RGB" and why what is? JPEG actually has the ability to store RGB data for high quality images (JPEGs out of a DSLR for example).

farindk commented 5 years ago

Yes, HEVC supports GBR, but it also does not mandatorily indicate that this color space is used. There is a field matrix_coefficients in the VUI header, which can be set to identity, but that header is not mandatory (standard says: "Conforming decoders are not required to process this [VUI] information for output order conformance to this Specification") and setting it to identity also does not clearly state that GBR is used, as the standard says: "The identity matrix. Typically used for GBR (often referred to as RGB); however, may also be used for YZX (often referred to as XYZ)"

dbtsai commented 5 years ago

@kliberty I know JPEG supports both RGB and YUV. I meant in ImageMagick when I use identify -verbose to get the metadata of jpeg files, it will always show RGB. I think the decoder is implemented in a way that always outputting RGB data and hiding the color space conversion details.

kliberty commented 5 years ago

@dbtsai Ah, there's two different colorspaces reported by identify -verbose. Wikipedia will have a better description than I can give, but Colorspace: sRGB is like saying "how red is red-255" as well as defining a the gamma curve. jpeg:colorspace: 2 which I found documented here (copied below) is the internal encoding like YCbCr and friends. I want to say that the default is BT 601 full range. jpeg:sampling-factor: 2x2,1x1,1x1 is the sub-sampling. I know this one means 4:2:0, but tbh I don't understand why or what the other combinations would mean.

0 = Bi-level
1 = YCbCr, ITU-R BT 709, video
2 = No color space specified
3 = YCbCr, ITU-R BT 601-1, RGB
4 = YCbCr, ITU-R BT 601-1, video
8 = Gray-scale
9 = PhotoYCC
10 = RGB
11 = CMY
12 = CMYK
13 = YCCK
14 = CIELab
dbtsai commented 5 years ago

@kliberty thanks for the information.

@farindk Beside the lossy colorspace conversion, I think there might be something libheif can improve. I used MacOSX's preview to convert a png file to heic (lossless) and then convert it back to png, the mae distortion is 72.8497 (0.00111161), while I used libheif lossless mode, the distortion is 352.814 (0.0053836).

I did a little bit research, and know that for jpegxr or jpeg2000's lossless mode, they use lossless YUV colorspace conversion. Is it possible to implement that in libheif?

Thanks.

farindk commented 5 years ago

@dbtsai: I'll check our colorspace conversions. Maybe there are some rounding errors that lead to the higher distortion.

Those lossless YUV conversions are not supported in the HEIF standard (yet). If we added them, it would be proprietary extensions and you would not be able to read those images in other software.

dbtsai commented 5 years ago

@farindk any new finding and have ImageMagick supporting colorspace? Thanks.

wdhwg001 commented 4 years ago

Hey I don't know if this is the right place to ask but, could we achieve lossless by using YUV444 10bits or higher bits? PNG is only a 8bit RGB format.

oghaki commented 4 years ago

No, that still won't work because the set of color points available in a color space encoded using RGB aren't the same as YUV because different parameters are used to define those points. Recall, RGB and YUV are encoding schemes, each using three parameters—for RGB, ('red' = R, 'green' = G, 'blue' = B) and, for YUV, ('luma' = Y, 'blue projection' = U, 'red projection' = V). Alone, these are just different coordinate systems to measure color values, and they only point to specific colors when minimum/maximum values for each parameter are assigned to an actual color (we'll assume a Euclidean space for now, but we'd also need a description of the topology of the space to know how steps relate to changes in the real values for the parameters, e.g. in a Euclidean space, the gap between 2 and 3 is the same as 3 and 4, and so-on, but non-Euclidean spaces are possible, and, indeed, we generate the equivalent of a non-Euclidean mapping with the gamma function, or various other OETFs or EOTFs). This is accomplished by applying the encoding over a specific volume of color, which is then parameterized by the parameters of our encoding system. If we could encode using continuous values (i.e. real numbers) for our parameters, then no conversion problem would arise, because both encoding systems would cover every point in the entire volume, but this would require an infinite bit depth, which is not going to work out. The bit depth just tells us how many different values the parameters can take on. If we assume an 8-bit system, starting at 0, our max values for our parameters will be 255 (i.e. 256 different steps). The actual mappings are a bit more complicated and vary between systems—e.g. HDR10 (and other PQ, "perceptual quantizer", systems) maps parameter values to real-world brightness values, in contrast to sRGB, which maps those same values to voltages on a device, but both do so non-linearly. For our purposes, we'll just ignore this variety, and say that, for RGB, the point with all three components maxed out, (Rₘₐₓ, Gₘₐₓ, Bₘₐₓ) = (255, 255, 255) = W, gives us a neutral color, and so points to what we'd call 'white' in the chosen volume of color (in real devices, the true max values need to be calibrated to the "white" we're calling neutral, e.g. D65). Likewise, minimum values, (Rₘᵢₙ, Gₘᵢₙ, Bₘᵢₙ) = (0, 0, 0) = K, point to our neutral "black". Using the same volume, our YUV white is the same W = (255, 0, 0), and black, k = (0, 0, 0) (we'll allow U and V to span from -128 to 127, with 0 being neutral). The set of all points defined by every possible combination of value for each parameter defines the color space. Both sets will have the same number of points, but they will not be the same points. Under the system laid out above, the 256 different shades of grey, from K to W, would appear in the intersection of both sets, but the vast majority, if not all, of the other ≈16.7 million points in each system will be in slightly different locations in space, resulting in error when we convert between them. Increasing the bit depth decreases this error, although I'm not sure whether any finite bit depth would be sufficient for error-free conversion. The same problem in one dimension is the use of different base numeral systems to represent values on a number line. Compare base-10 (decimal) vs base-2 (binary). In general, there will be some values that neither system can represent with a literal decimal notation (or even a fraction), irrational numbers, e.g. √2 or π. However, some numbers are a limitation of the numeral system. For example, neither system can represent ¹⁄₃ using decimal notation. In contrast, ¹⁄₁₀ is easily represented in base-10 (0.1), but it cannot be represented in binary—you end up with the repeating binary decimal, 0.00011... . We can, however, estimate base-10 decimal, 0.1, in binary, and the higher the bit-depth (i.e. the more decimal places we have), the more precisely we'll be able to estimate it. The same is true when using different, mutually incoherent encoding systems.

GregSlazinski commented 3 years ago

Is there any news on this? How to achieve lossless RGB compression?

farindk commented 3 years ago

Yes, use heif_enc --matrix_coefficients=0 -p chroma=444 to save in RGB space.

GregSlazinski commented 3 years ago

how to do that through C api (codes)?

farindk commented 3 years ago

At the moment, you'll have to use the C function for that (#305). Like this:

heif_color_profile_nclx nclx;
nclx.matrix_coefficients = (heif_matrix_coefficients) nclx_matrix_coefficients;
nclx.transfer_characteristics = (heif_transfer_characteristics) nclx_transfer_characteristic;
nclx.color_primaries = (heif_color_primaries) nclx_colour_primaries;
nclx.full_range_flag = (uint8_t) nclx_full_range;

heif_image_set_nclx_color_profile(image.get(), &nclx);
GregSlazinski commented 3 years ago

Hello,

Before change, ~99% of pixels in HEIF compressed images were different.

After this change:

                  heif_color_profile_nclx nclx;
                  nclx.version=1;
                  nclx.matrix_coefficients     =heif_matrix_coefficients_RGB_GBR;
                  nclx.transfer_characteristics=heif_transfer_characteristic_unspecified;
                  nclx.color_primaries         =heif_color_primaries_unspecified;
                  nclx.full_range_flag         =true;
                  heif_image_set_nclx_color_profile(image, &nclx);

Following things happened: -only around 25% pixels are different, but still different, meaning compression is not lossless -On Windows File Explorer and XnViewMP the file got purple tint

So it doesn't work correctly, and I would recommend reopening this issue.

I'm using heif_colorspace_RGB, heif_chroma_interleaved_RGB, heif_channel_interleaved

farindk commented 3 years ago

Did you also set the encoder parameters to lossless?

On Windows File Explorer and XnViewMP the file got purple tint

I guess that the Windows decoder cannot handle matrix_coefficients=0. We have tested this against libavif and Chrome (for AVIF, but it's the same for HEIF).

GregSlazinski commented 3 years ago

Yes I did.

Here's my full code: below: q=100;

      if(heif_context *ctx=heif_context_alloc())
      {
         heif_encoder *encoder=null; heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder); if(encoder)
         {
            Int q=RoundPos(quality*100);
            if( q<  0)q=100;else // default to 100=lossless
            if( q>100)q=100;

            heif_encoder_set_lossy_quality(encoder, q);
            heif_encoder_set_lossless     (encoder, q>=100);

            enum TYPE
            {
               MONO,
               RGB ,
               RGBA,
            }type;
          C ImageTypeInfo &type_info=src->typeInfo();
          //if(type_info.channels<=1)type=MONO;else dunno how to configure libheif to make this work
            if(type_info.a          )type=RGBA;else
                                     type=RGB ;

            heif_image *image=null; heif_image_create(src->w(), src->h(), (type==MONO) ? heif_colorspace_monochrome : heif_colorspace_RGB, (type==MONO) ? heif_chroma_monochrome : (type==RGB) ? heif_chroma_interleaved_RGB : heif_chroma_interleaved_RGBA, &image); if(image)
            {
               if(q>=100) // lossless
               {
                  heif_color_profile_nclx nclx;
                  nclx.version                 =1;
                  nclx.matrix_coefficients     =heif_matrix_coefficients_RGB_GBR;
                  nclx.transfer_characteristics=heif_transfer_characteristic_unspecified;
                  nclx.color_primaries         =heif_color_primaries_unspecified;
                  nclx.full_range_flag         =true;
                  heif_image_set_nclx_color_profile(image, &nclx);
               }

               heif_image_add_plane(image, heif_channel_interleaved, src->w(), src->h(), 8);
               int stride; if(uint8_t *dest=heif_image_get_plane(image, heif_channel_interleaved, &stride))
               {
                  Bool direct_type;
                  switch(type)
                  {
                     case MONO: direct_type=(src->hwType()==IMAGE_R8       || src->hwType()==IMAGE_L8            || src->hwType()==IMAGE_A8 || src->hwType()==IMAGE_I8); break;
                     case RGB : direct_type=(src->hwType()==IMAGE_R8G8B8   || src->hwType()==IMAGE_R8G8B8_SRGB  ); break;
                     case RGBA: direct_type=(src->hwType()==IMAGE_R8G8B8A8 || src->hwType()==IMAGE_R8G8B8A8_SRGB); break;
                  }
                  if(direct_type)
                  {
                   C Byte *src_data=src->data();
                     if(stride==src->pitch())
                     {
                        CopyFast(dest, src_data, src->pitch()*src->h());
                     }else
                     {
                        Int copy=Min(stride, src->pitch());
                        REP(src->h())
                        {
                           CopyFast(dest, src_data, copy);
                           dest    +=stride;
                           src_data+=src->pitch();
                        }
                     }
                  }else
                  {
                     FREPD(y, src->h())
                     {
                        Byte *out=dest;
                        FREPD(x, src->w())switch(type)
                        {
                           case MONO: *(Byte *)out=FltToByte(src->pixelF(x, y)  ); out+=SIZE(Byte ); break;
                           case RGB : *(VecB *)out=          src->color (x, y).v3; out+=SIZE(VecB ); break;
                           case RGBA: *(Color*)out=          src->color (x, y)   ; out+=SIZE(Color); break;
                        }
                        dest+=stride;
                     }
                  }

                  if(heif_context_encode_image(ctx, image, encoder, null, null).code==heif_error_Ok)
                  {
                     heif_writer writer;
                     writer.writer_api_version=1;
                     writer.write=HEIFWrite;
                     if(heif_context_write(ctx, &writer, &f).code==heif_error_Ok)ok=f.ok();
                  }
               }
               heif_image_release(image);
            }
            heif_encoder_release(encoder);
         }
         heif_context_free(ctx);
      }
farindk commented 3 years ago

Ok, I'll try this too.

farindk commented 3 years ago

I have tried the following with a couple of images

heif-enc input.png -o output.heif -p lossless=true --matrix_coefficients=0 -p chroma=444
heif-convert output.heif output.png
convert input.png input.ppm
convert output.png output.ppm
diff input.ppm output.ppm

All images with no difference so far.

GregSlazinski commented 3 years ago

is chroma=444 needed? in my tests above I've used heif_colorspace_RGB, heif_chroma_interleaved_RGB, heif_channel_interleaved I'll try heif_chroma_444 now

farindk commented 3 years ago

Take care: chroma=444 is an encoder option to use 4:4:4 in the encoded bitstream. It is not used here as a definition of the input color space.

farindk commented 3 years ago

Use

heif_encoder_set_parameter_string(heif_encoder*, "chroma", "444");
GregSlazinski commented 3 years ago

Thank you,

This code

               {
                  heif_color_profile_nclx nclx;
                  nclx.version                 =1;
                  nclx.matrix_coefficients     =heif_matrix_coefficients_RGB_GBR;
                  nclx.transfer_characteristics=heif_transfer_characteristic_unspecified;
                  nclx.color_primaries         =heif_color_primaries_unspecified;
                  nclx.full_range_flag         =true;
                  heif_image_set_nclx_color_profile(image, &nclx);

                  heif_encoder_set_parameter_string(encoder, "chroma", "444");
               }

Does indeed provide RGB lossless results, however the problem now is, that these options made the output file much bigger. With options above: a test file is 731 KB (heif_image_set_nclx_color_profile and heif_encoder_set_parameter_string), result is lossless Without options above: size is 330 KB, but result is not lossless With only heif_image_set_nclx_color_profile: size 409 KB, but result is not lossless (all 3 tests above have heif_encoder_set_lossless(encoder, true); )

And the same image using WEBP lossless gives: 642 KB

So to achieve true RGB lossless compression, WEBP is better than HEIF, which is kinda disappointing.

Or perhaps the codes above just give inefficient configuration? Could different configuration be used to achieve lossless RGB with smaller size?

farindk commented 3 years ago

h265 and especially x265 are probably not very optimized for lossless coding. You can further tweak x265 encoder parameters to spend more time on finding better compressions. Try e.g.

heif_encoder_set_parameter_int(heif_encoder*, "complexity", 100);

Or these parameters

  preset, default=slow, { ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow,placebo }
  tune, default=ssim, { psnr,ssim,grain,fastdecode }
  tu-intra-depth, default=2, [1;4]

Note that you can also set any other x265 parameter by using the original x265 parameter name with a "x265:" prefix in libheif.

If you find a good combination, please let us know the settings. We might then add them to libheif.

GregSlazinski commented 3 years ago

I've tried the following options:

heif_encoder_set_parameter_integer(encoder, "complexity", 100);
heif_encoder_set_parameter_string (encoder, "preset"    , "veryslow");
heif_encoder_set_parameter_string (encoder, "tune"      , "psnr");
heif_encoder_set_parameter_integer(encoder, "tu-intra-depth", 4);

But they didn't make any difference in the file size.

farindk commented 3 years ago

@ValZapod Depends on the matrix_coefficients. If matrix_coefficients=0, you are effectively coding in RGB, because Y=G, Cb=B, Cr=R.

astiob commented 3 years ago

@ValZapod chroma=4:4:4 is also needed. Chroma subsampling introduces loss, and a nontrivial color matrix also introduces loss. They’re controlled independently, and both must be disabled.


@farindk I hate to resurrect this, but I can’t seem to be able to losslessly round-trip my 8-bit PNGs. I’ve tried libheif 1.12.0 and master; no difference. I’m using (with x265 3.5, libde265 1.0.8, aom 3.1.1):

heif-enc -L -p chroma=444 --matrix_coefficients=0 -o test.heif test-orig.png
heif-convert test.heif test-heif.png
compare test-heif.png test-orig.png compare.png

The images look very similar, but compare reveals that they’re not identical. (I’ve just tried converting to PPM and diffing: also shows a difference.)

Switching between HEIF and AVIF makes no difference: both round-tripped PNGs are identical, and both are different from the original. This leads me to think libheif is the culprit.

I’ve tried encoding and decoding the round-tripped PNG again: the resulting image is once again different (although fewer pixels change than the first time around). This suggests that my source PNG doesn’t somehow contain unrepresentable pixels, as much as I don’t think such pixels should exist at all with full-range matrix_coefficients=0.

I’ve tried an RGBA source image and an RGB source image without an alpha channel: both are problematic.

I’ve tried stripping the PNGs, before and after encoding and comparing, of colorspace-related chunks: no difference.

I’ve tried adding -q <number> to heif-convert: no difference.

I’ve tried adding -p x265:colormatrix=gbr, but that gives an obvious green tint to the round-tripped image. I’ve tried adding a variety of other options, but none made any difference.

Adding -v confirms that x265 is using 4:4:4 subsampling and seemingly in lossless mode. I can’t confirm that the full range is being used (but at the very least, adding -p x265:range=full doesn’t solve the problem).

Type Source Round-tripped Difference
RGBA test-orig test-heif compare
RGB noalpha-orig noalpha-heif compare
astiob commented 3 years ago

You forgot -p lossless=true.

-L does that. And I did try adding it manually: no difference.

astiob commented 3 years ago

No, it is not, using chroma subsampling for RGB JPEG and family is possible but looks just dumb and not supported even by ffmpeg in full.

I don’t understand what JPEG or FFmpeg or dumbness has to do with this.

If you encode with chroma subsampling, even with the GBR matrix, you’re still subsampling chroma and losing information. If you want a lossless round-trip of a full-pixel RGB image, you need to use 4:4:4 chroma and you need to ensure the encoder doesn’t lose information to non-integer matrix conversions.

YOU SHOULD NEVER CALL RGB 4:4:4 though. RGB 4:2:2 is possible, and in that case you can call it that.

Nobody said that “RGB” is the same as “4:4:4”. In fact, HEIF doesn’t directly encode anything named “RGB” at all. Whatever you do, you’re always encoding luma and chroma, and the chroma can be subsampled, but you can choose what the luma and chroma mean in relation to RGB.

Given RGB input, the encoder must convert it to luma and appropriately-subsampled chroma. If you tell it to use 4:2:0 chroma (which is the default), it will do so. If you tell it to use BT.601 YCbCr (which is the default), it will do so. If you want to avoid data loss, you need to choose options for each of these that match your source. For a 24-bit RGB source, the only lossless combinations are same-bit-depth 4:4:4 GBR (where luma is set to G and chroma is set to B and R without any colour mixing) and higher-bit-depth 4:4:4 YCgCo (where the 8-bit colour channels are mixed into 9-bit values resembling traditional luma and chroma that can be exactly un-mixed back into the original 24-bit RGB).

If you’re trying to say that encoding GBR with subsampled chroma doesn’t make sense, HEIF doesn’t care: what’s allowed is allowed. And eventually someone somewhere may find it useful.

If you’re saying that heif-enc would be more user-friendly if its --lossless defaulted to lossless chroma and matrix settings for the given source image, I agree. But it doesn’t, and it doesn’t change the fact it would need to select 4:4:4 chroma for 24-bit sources.

astiob commented 3 years ago

It makes no sense.

So you agree that you want to turn the subsampling off. -p chroma=444 is how you turn it off.

You cannot call R, G, B chroma and luma! This IS JUST OUTRAGEUSLY wrong.

If it makes you feel better, you can say “channel one”, “channel two” and “channel three”. The point is that the format allows subsampling channels two and three, and heif-enc defaults to subsampling them 4:2:0. The format doesn’t care what the channels really represent: not caring makes it simpler and more universal. H.264, H.265, HEIF, AV1, AVIF all work like this.

And you need to use 10 bit for 8 bit RGB. 8 bit YCbCr is not enough for 8 bit RGB to be lossless. OMG.

No amount of bits will make conversion between RGB and true(ish) YCbCr lossless: the resulting numbers are (in general) fractional and can’t be represented exactly, and they can go out of range (below zero or above 255). But for GBR, 8 bits are enough. For YCgCo… you got me:

That is called YCoCg-R, I suppose?

I’ve double-checked H.273: it actually supports both what Wikipedia calls “YCoCg” and what it calls “YCoCg-R”.

(Wikipedia uses “YCoCg”; H.273 uses “YCgCo”. The only difference is the channel order.)

“YCoCg”, in fact, needs 10 bits for an 8-bit RGB source. It is used if all encoded channels have the same bit depth.

“YCoCg-R” is used when luma and chroma bit depths differ, and it is more efficient: it needs just 8 bits for luma and 9 bits for chroma. But I don’t think x264 or x265 (not sure about other encoders) actually support encoding luma and chroma at different depths.

There are many variants of that -R too, so...

H.273 (and earlier H.264) defines exactly what conversions are supported in H.265 and AV1 (and effectively H.264*). You can’t supply an arbitrary matrix; you just choose one from the standard list.

* My knowledge is rusty: maybe the latest editions of H.264 reference H.273. IIRC earlier editions defined this on their own, but the tables are the same.

I opened a new issue #533

Thanks! I’ll try to bisect this to find where exactly the regression happened.

jsshapiro commented 1 year ago

A suggestion for improving this discussion, if I may intrude. This discussion is persistently terms and concepts, which is leading to confusion, errors, and miscommunications.

For starters, RGB is not a color space. It is a color model. RGB simply says that there is an R value, a G value, and a B value; it says nothing about what these values mean, how many bits are involved in each, how the bits are interpreted against the perceptual range of the color channel, or how two different RGB values are related. sRGB is a color space. It describes a specific mapping of RGB values into the human-perceived color gamut, and has a well-established mapping to the CIE tristimulus system. CIELAB is arguably both. It specifies colors on three axes that have a uniform encoding and a direct mapping to the CIEXYZ tristimulus model.

So when we say "Saving in RGB colorspace", what we most likely mean is "Saving in the sRGB (or _Adobe RGB) color space, encoded using an RGB color model".

Likewise, there is discussion here about chroma and luma. While there are color models that state colors in terms of lightness, chroma, and hue, the discussion of lightness and chroma here is about mapping RGB colors into more efficient space suitable for encoding (in the discussion above: YCbCr).

I suggest that the discussion would improve if we maintain a clear distinction between color spaces, color models, and concerns related to color encoding or color conversion. Mixing these up more or less guarantees confusion.

bigcat88 commented 8 months ago

Is this issue is still relevant? Cause for me last two libheif major versions(1.16,1.17) works to save in lossless both for AVIF/HEIF

kleisauke commented 8 months ago

Indeed, I think this was fixed at some point. For example, with libheif v1.17.3 and aom v3.7.0 I see:

$ heif-enc input.png -o x.avif -L -p chroma=444 --matrix_coefficients=0
$ heif-convert x.avif output.png
File contains 1 image
Written to output.png
$ vips subtract input.png output.png x.v
$ vips abs x.v x2.v
$ vips max x2.v
0.000000

i.e. the maximum absolute difference between writes is now zero, as long as you pass -L -p chroma=444 --matrix_coefficients=0. https://github.com/strukturag/libheif/blob/c5ea21eb51d8cd14f592cff64f8b179707e45592/examples/heif_enc.cc#L184-L187

farindk commented 7 months ago

In the devel-v1.18.0 branch, I've slightly changed the semantics of the heif-enc --lossless / -L flag (ee6990776ddbf508e500ae1f9b6c66ec658b4cde, 94c3b21d8df09b69c2c1752752e1810fd902d7d7). When this flag is given, all parameters are set such that the input image will be encoded losslessly. I.e. for RGB inputs, it sets matrix_coefficients=0, chroma=444. For YCbCr inputs, this makes sure that the output NCLX is equal to the input NCLX and that the same chroma formats are used for encoding.

Note that this is different from the old meaning to only enable lossless encoding for the codec. If you want that, you can still set the encoder parameter individually with -p lossless=true.