jpeg-encoder icon indicating copy to clipboard operation
jpeg-encoder copied to clipboard

Function to get maximum output buffer size

Open jsonmona opened this issue 2 years ago • 3 comments

A function to get maximum possible output buffer size would be useful. Common usecase would be pre-allocating destination buffer.

libjpeg-turbo has implemented the function as:

/* TurboJPEG 1.0+ */
DLLEXPORT unsigned long TJBUFSIZE(int width, int height)
{
  static const char FUNCTION_NAME[] = "TJBUFSIZE";
  unsigned long long retval = 0;

  if (width < 1 || height < 1)
    THROWG("Invalid argument", (unsigned long)-1);

  /* This allows for rare corner cases in which a JPEG image can actually be
     larger than the uncompressed input (we wouldn't mention it if it hadn't
     happened before.) */
  retval = PAD(width, 16) * PAD(height, 16) * 6ULL + 2048ULL;
  if (retval > (unsigned long long)((unsigned long)-1))
    THROWG("Image is too large", (unsigned long)-1);

bailout:
  return (unsigned long)retval;
}

If I were to translate it into Rust, I would write:

fn max_buffer_size(width: u16, height: u16) -> Option<usize> {
    let padded_w = (width as usize).checked_next_multiple_of(16)?;
    let padded_h = (height as usize).checked_next_multiple_of(16)?;

    padded_w.checked_mul(padded_h)?.checked_mul(6)?.checked_add(2048)
}

Note that first two checked_next_multiple_of can be replaced with normal next_multiple_of assuming 32-bit usize.

jsonmona avatar Oct 22 '23 15:10 jsonmona

Sorry for the late reply.

I'm not against this kind of utilities, but I'm wondering how useful this is in the context of Rust.

Feel free to send a PR if you need this. To make the function more useful I think it would be better to use the number of components and maybe the sampling rate in the calculation. In addition, i would prefer an u64 instead of Option<usize> as the return type. Even on 32 bit systems it's useful to get the real size (i.e. for error messages) and it would eliminate the need for the checked functions in the calculations.

vstroebel avatar Jan 30 '24 09:01 vstroebel

Sorry for the late reply.

No problem. 😁

I'm not against this kind of utilities, but I'm wondering how useful this is in the context of Rust.

My usecase is to encode several images with fixed resolution, and send it via network. As I keep the encoded bitstream in memory, it looked like a better idea to allocate a large enough buffer upfront and use it repeatedly. In addition, I can estimate maximum required bandwidth and show it to user.

To make the function more useful I think it would be better to use the number of components and maybe the sampling rate in the calculation.

I admit that the proposed function is quite naive. The provided link has other functions that seem to do what you say. Just visit the link and scroll down a bit. I haven't read it yet, but the function name sounds promising.

In addition, i would prefer an u64 instead of Option as the return type. Even on 32 bit systems it's useful to get the real size (i.e. for error messages) and it would eliminate the need for the checked functions in the calculations.

Agreed. I checked that u64 (or even i64) is indeed enough for the output.

So, yeah. I'll try to come up with a PR within a month.

jsonmona avatar Feb 02 '24 06:02 jsonmona

I have implemented it here.

The function looks like:

pub fn max_output_size(width: u16, height: u16, sampling_factor: SamplingFactor) -> u64 {
    let (mcu_width, mcu_height) = sampling_factor.mcu_size();

    let padded_w = (width as u64).next_multiple_of(mcu_width as u64);
    let padded_h = (height as u64).next_multiple_of(mcu_height as u64);

    // This term should be zero when using grayscale subsampling
    let chroma = 4 * 64 / (mcu_width as u64 * mcu_height as u64);

    padded_w * padded_h * (2 + chroma) + 2048
}

But there's a problem.

Although I'm not a lawyer, I believe translating code from libjpeg-turbo is considered a derivative work, which in turn requires copying and respecting the original license, which happens to be the Modified (3-clause) BSD License.

As far as I can tell, the original function (tj3JPEGBufSize) is irrelevant from lihjpeg API, so there is no need to respect IJG license. (Again, I'm not a lawyer.)

As even I'm not sure if this is correct, I'm hesitant to open the PR. In fact, I'm even considering closing this issue altogether. What do you think?

jsonmona avatar Feb 18 '24 15:02 jsonmona