Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  nsJXLDecoder.cpp   Sprache: C

 

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "ImageLogging.h"  // Must appear first

#include "nsJXLDecoder.h"

#include "AnimationParams.h"
#include "mozilla/CheckedInt.h"
#include "gfxPlatform.h"
#include "RasterImage.h"
#include "SurfacePipeFactory.h"

using namespace mozilla::gfx;

namespace mozilla::image {

static LazyLogModule sJXLLog("JXLDecoder");

nsJXLDecoder::nsJXLDecoder(RasterImage* aImage) : Decoder(aImage) {
  MOZ_LOG(sJXLLog, LogLevel::Debug,
          ("[this=%p] nsJXLDecoder::nsJXLDecoder", this));
}

nsresult nsJXLDecoder::InitInternal() {
  bool hasCMS = GetCMSOutputProfile() && mCMSMode != CMSMode::Off;
  mDecoder.reset(jxl_decoder_new(IsMetadataDecode(), hasCMS));
  if (WantsFrameCount()) {
    mScanner.reset(jxl_scanner_new());
  }
  return NS_OK;
}

nsJXLDecoder::~nsJXLDecoder() {
  MOZ_LOG(sJXLLog, LogLevel::Debug,
          ("[this=%p] nsJXLDecoder::~nsJXLDecoder", this));
}

LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
                                   IResumable* aOnResume) {
  MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");

  if (WantsFrameCount()) {
    return ScanForFrameCount(aIterator, aOnResume);
  }

  while (true) {
    if (mIteratorComplete) {
      return DrainFrames();
    }

    if (!aIterator.IsReady() || aIterator.Length() == 0) {
      SourceBufferIterator::State state =
          aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume);

      if (state == SourceBufferIterator::WAITING ||
          state == SourceBufferIterator::COMPLETE) {
        // We are about to suspend until more bytes arrive, so this is the
        // point where flushing a partial frame is actually useful for the
        // user. NeedMoreData inside ProcessAvailableData does not imply this:
        // the iterator may still have buffered bytes that we will consume on
        // the next loop iteration in ProcessAvailableData. This is also
        // potentially the only and last chance to actually push the pixels
        // through the surface pipe so they get to the user.
        if (!HasAnimation() && !mPixelBuffer.empty()) {
          FlushPartialFrame();
        }
      }

      if (state == SourceBufferIterator::WAITING) {
        return LexerResult(Yield::NEED_MORE_DATA);
      }
      if (state == SourceBufferIterator::COMPLETE) {
        mIteratorComplete = true;
        continue;
      }
    }

    const uint8_t* data = reinterpret_cast<const uint8_t*>(aIterator.Data());
    size_t length = aIterator.Length();
    MOZ_ASSERT(length > 0);
    const uint8_t* const chunkStart = data;

    // data and length are in/out: updated to reflect the unconsumed remainder.
    ProcessResult result = ProcessAvailableData(&data, &length);

    aIterator.MarkConsumed(static_cast<size_t>(data - chunkStart));

    switch (result) {
      case ProcessResult::Error:
        return LexerResult(TerminalState::FAILURE);
      case ProcessResult::Complete:
        return LexerResult(TerminalState::SUCCESS);
      case ProcessResult::NeedMoreData:
        break;
      case ProcessResult::YieldOutput:
        return LexerResult(Yield::OUTPUT_AVAILABLE);
    }
  }
}

LexerResult nsJXLDecoder::ScanForFrameCount(SourceBufferIterator& aIterator,
                                            IResumable* aOnResume) {
  MOZ_ASSERT(mScanner);

  const uint8_t* currentData = nullptr;
  size_t currentLength = 0;
  bool iteratorComplete = false;

  while (true) {
    // Only fetch a new chunk when all buffered bytes have been consumed.
    if (currentLength == 0 && !iteratorComplete) {
      SourceBufferIterator::State state =
          aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume);

      if (state == SourceBufferIterator::WAITING) {
        return LexerResult(Yield::NEED_MORE_DATA);
      }

      if (state == SourceBufferIterator::READY) {
        currentData = reinterpret_cast<const uint8_t*>(aIterator.Data());
        currentLength = aIterator.Length();
      }

      if (state == SourceBufferIterator::COMPLETE) {
        iteratorComplete = true;
      }
    }

    JxlDecoderStatus scanStatus = jxl_decoder_process_data(
        mScanner.get(), ¤tData, ¤tLength,
        /*pixel_buffer=*/nullptr, 0, /*k_buffer=*/nullptr, 0);
    if (scanStatus == JxlDecoderStatus::Error) {
      return LexerResult(TerminalState::FAILURE);
    }

    if (!HasSize()) {
      JxlBasicInfo info = jxl_decoder_get_basic_info(mScanner.get());
      if (info.valid) {
        if (info.width > INT32_MAX || info.height > INT32_MAX) {
          return LexerResult(TerminalState::FAILURE);
        }
        PostSize(info.width, info.height);
        if (info.has_alpha) {
          PostHasTransparency();
        }
        if (!info.is_animated) {
          PostFrameCount(1);
          return LexerResult(TerminalState::SUCCESS);
        }
      }
    }

    // Post animation info once we have at least one scanned frame.
    if (HasSize() && !HasAnimation()) {
      // If we get here we know that we are animated but we haven't called
      // PostIsAnimated yet because we return above if we are not animated.
      uint32_t scannedCount =
          jxl_decoder_get_scanned_frame_count(mScanner.get());
      if (scannedCount > 0) {
        JxlBasicInfo info = jxl_decoder_get_basic_info(mScanner.get());
        JxlFrameInfo frameInfo =
            jxl_decoder_get_scanned_frame_info(mScanner.get(), 0);
        PostIsAnimated(
            FrameTimeout::FromRawMilliseconds(frameInfo.duration_ms));
        // num_loops == 0 in jxl means infinite loop, whereas 1 means one total
        // play through.
        PostLoopCount((info.num_loops == 0 || info.num_loops > INT32_MAX)
                          ? -1
                          : static_cast<int32_t>(info.num_loops - 1));
      }
    }

    if (HasSize()) {
      uint32_t count = jxl_decoder_get_scanned_frame_count(mScanner.get());
      if (count >= 1) {
        PostFrameCount(count);
      }
    }

    if (HasSize() && !jxl_decoder_has_more_frames(mScanner.get())) {
      return LexerResult(TerminalState::SUCCESS);
    }

    // If the scanner needs more data and the iterator is exhausted with no
    // buffered bytes remaining, finalize with what we have.
    if (scanStatus == JxlDecoderStatus::NeedMoreData && currentLength == 0 &&
        iteratorComplete) {
      if (!HasSize()) {
        return LexerResult(TerminalState::FAILURE);
      }
      uint32_t count = jxl_decoder_get_scanned_frame_count(mScanner.get());
      PostFrameCount(count > 0 ? count : 1);
      return LexerResult(TerminalState::SUCCESS);
    }
  }
}

// aData and aLength are in/out: on return they point to and describe the
// unconsumed remainder of the input.
nsJXLDecoder::ProcessResult nsJXLDecoder::ProcessAvailableData(
    const uint8_t** aData, size_t* aLength) {
  while (true) {
    JxlDecoderStatus status = ProcessInput(aData, aLength);

    if (status == JxlDecoderStatus::Error) {
      return ProcessResult::Error;
    }

    // Basic info may have just become available, regardless of whether
    // ProcessInput returned Ok or NeedMoreData (jxl-rs sets it during its
    // internal loop and continues). Posting the size and pre-allocating
    // the pixel buffer and SurfacePipe before the visible frame header
    // arrives is what lets flush_pixels render an LF-frame preview into
    // the buffer while we are still waiting on bytes.
    if (mDecoderState == DecoderState::Initial) {
      JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
      if (basicInfo.valid) {
        if (basicInfo.width > INT32_MAX || basicInfo.height > INT32_MAX) {
          return ProcessResult::Error;
        }

        PostSize(basicInfo.width, basicInfo.height);
        if (basicInfo.has_alpha) {
          PostHasTransparency();
        }

        if (!basicInfo.is_animated) {
          PostFrameCount(1);
          if (IsMetadataDecode()) {
            return ProcessResult::Complete;
          }
        }

        mDecoderState = DecoderState::HaveBasicInfo;

        // Allocate the pixel buffer for non-animated images here so that
        // FlushPartialFrame can render an LF preview during the LF-frame
        // phase. The SurfacePipe (and the surface it exposes) is created
        // lazily in FlushPartialFrame / FinishFrame so that no opaque-black
        // surface is visible while we wait for the first rendered pixels.
        // Animated images don't take the FlushPartialFrame path, so eager
        // allocation buys them nothing; each frame's buffer is allocated
        // per-frame in HandleFrameOutput.
        if (!basicInfo.is_animated) {
          if (NS_FAILED(AllocateFrameBuffers())) {
            return ProcessResult::Error;
          }
        }
      } else if (status == JxlDecoderStatus::Ok) {
        // Ok without basic info shouldn't happen, but if it does, retry
        // with the remaining bytes.
        if (*aLength == 0) {
          return ProcessResult::NeedMoreData;
        }
        continue;
      }
    }

    if (status == JxlDecoderStatus::NeedMoreData) {
      return ProcessResult::NeedMoreData;
    }

    MOZ_ASSERT(status == JxlDecoderStatus::Ok);
    if (mDecoderState == DecoderState::HaveBasicInfo) {
      if (jxl_decoder_is_frame_ready(mDecoder.get()) && !HasAnimation()) {
        JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
        if (basicInfo.is_animated) {
          JxlFrameInfo frameInfo = jxl_decoder_get_frame_info(mDecoder.get());
          PostIsAnimated(
              FrameTimeout::FromRawMilliseconds(frameInfo.duration_ms));
          // num_loops == 0 in jxl means infinite loop, whereas 1 means one
          // total play through.
          PostLoopCount(
              (basicInfo.num_loops == 0 || basicInfo.num_loops > INT32_MAX)
                  ? -1
                  : static_cast<int32_t>(basicInfo.num_loops - 1));
          if (IsMetadataDecode()) {
            return ProcessResult::Complete;
          }
        }
      }
    }

    switch (HandleFrameOutput()) {
      case FrameOutputResult::BufferAllocated:
      case FrameOutputResult::NoOutput:
        continue;
      case FrameOutputResult::FrameAdvanced:
        return ProcessResult::YieldOutput;
      case FrameOutputResult::DecodeComplete:
        return ProcessResult::Complete;
      case FrameOutputResult::Error:
        return ProcessResult::Error;
    }
    MOZ_CRASH("Unhandled FrameOutputResult");
  }
}

LexerResult nsJXLDecoder::DrainFrames() {
  while (true) {
    const uint8_t* noData = nullptr;
    size_t noLength = 0;
    JxlDecoderStatus status = ProcessInput(&noData, &noLength);

    switch (status) {
      case JxlDecoderStatus::Ok: {
        if (!HasSize()) {
          return LexerResult(TerminalState::FAILURE);
        }

        switch (HandleFrameOutput()) {
          case FrameOutputResult::BufferAllocated:
            break;
          case FrameOutputResult::FrameAdvanced:
            return LexerResult(Yield::OUTPUT_AVAILABLE);
          case FrameOutputResult::DecodeComplete:
          case FrameOutputResult::NoOutput:
            return LexerResult(TerminalState::SUCCESS);
          case FrameOutputResult::Error:
            return LexerResult(TerminalState::FAILURE);
        }
        break;
      }

      case JxlDecoderStatus::NeedMoreData:
        if (!HasSize()) {
          return LexerResult(TerminalState::FAILURE);
        }
        return LexerResult(TerminalState::SUCCESS);

      case JxlDecoderStatus::Error:
        return LexerResult(TerminalState::FAILURE);
    }
  }
}

JxlDecoderStatus nsJXLDecoder::ProcessInput(const uint8_t** aData,
                                            size_t* aLength) {
  uint8_t* bufferPtr = mPixelBuffer.empty() ? nullptr : mPixelBuffer.begin();
  size_t bufferLen = mPixelBuffer.length();
  uint8_t* kPtr = mKBuffer.empty() ? nullptr : mKBuffer.begin();
  size_t kLen = mKBuffer.length();
  return jxl_decoder_process_data(mDecoder.get(), aData, aLength, bufferPtr,
                                  bufferLen, kPtr, kLen);
}

nsJXLDecoder::FrameOutputResult nsJXLDecoder::HandleFrameOutput() {
  bool frameNeedsBuffer = jxl_decoder_is_frame_ready(mDecoder.get());

  if (frameNeedsBuffer && mPixelBuffer.empty()) {
    if (NS_FAILED(AllocateFrameBuffers())) {
      return FrameOutputResult::Error;
    }
    return FrameOutputResult::BufferAllocated;
  }

  if (!frameNeedsBuffer && !mPixelBuffer.empty()) {
    nsresult rv = FinishFrame();
    if (NS_FAILED(rv)) {
      return FrameOutputResult::Error;
    }

    bool hasMoreFrames = jxl_decoder_has_more_frames(mDecoder.get());
    if (IsFirstFrameDecode() || !HasAnimation() || !hasMoreFrames) {
      PostFrameCount(mFrameIndex + 1);
      PostDecodeDone();
      return FrameOutputResult::DecodeComplete;
    }
    mFrameIndex++;
    mPixelBuffer.clear();
    mKBuffer.clear();
    return FrameOutputResult::FrameAdvanced;
  }

  return FrameOutputResult::NoOutput;
}

nsJXLDecoder::PixelFormat nsJXLDecoder::DetectPixelFormat(
    JxlApiDecoder* aDecoder, const JxlBasicInfo& aBasicInfo) {
  if (jxl_decoder_use_f16(aDecoder)) {
    return PixelFormat::Rgba16f;
  }
  if (jxl_decoder_is_gray(aDecoder)) {
    return aBasicInfo.has_alpha ? PixelFormat::GrayAlpha8 : PixelFormat::Gray8;
  }
  // Cmyk8 is set when a Black extra channel is present, regardless of CMS,
  // so the no-CMS fallback works too.
  return jxl_decoder_has_black_channel(aDecoder) ? PixelFormat::Cmyk8
                                                 : PixelFormat::Rgba8;
}

nsresult nsJXLDecoder::AllocateFrameBuffers() {
  MOZ_ASSERT(HasSize());
  OrientedIntSize size = Size();
  JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
  MOZ_ASSERT(basicInfo.valid);

  // Format is constant across all frames; detect once on the first frame.
  if (mFrameIndex == 0) {
    mPixelFormat.set(DetectPixelFormat(mDecoder.get(), basicInfo));
  }

  // These buffers are cleared in HandleFrameOutput after each frame is consumed
  // and resized here for the next frame. After the first frame the capacity is
  // already sufficient, so clear() + resize() is just two integer field updates
  // with no memory operations.
  CheckedInt<size_t> bufferSize =
      CheckedInt<size_t>(size.width) * size.height * BytesPerPixel();
  if (!bufferSize.isValid() || !mPixelBuffer.resize(bufferSize.value())) {
    MOZ_LOG(sJXLLog, LogLevel::Error,
            ("[this=%p] nsJXLDecoder::AllocateFrameBuffers -- "
             "failed to allocate pixel buffer\n",
             this));
    return NS_ERROR_FAILURE;
  }

  if (mPixelFormat.value() == PixelFormat::Cmyk8 &&
      !mKBuffer.resize(size_t(size.width) * size.height)) {
    return NS_ERROR_FAILURE;
  }

  // Per-row u8 scratch for all non-passthrough paths (gray, CMYK, HDR).
  // Rgba8 passes directly through the pipe; all other formats need conversion.
  // Cmyk8 qcms output is RGB8 (3 bytes/pixel) but we allocate 4 for uniformity.
  if (mPixelFormat.value() != PixelFormat::Rgba8) {
    CheckedInt<size_t> rowBufSize = CheckedInt<size_t>(size.width) * 4;
    if (!rowBufSize.isValid() || !mU8RowBuf.resize(rowBufSize.value())) {
      return NS_ERROR_FAILURE;
    }
  }

  // Build the qcms transform once on the first frame. The ICC profile and
  // color space are constant across all frames of a JXL image.
  if (mFrameIndex == 0 && GetCMSOutputProfile() && mCMSMode != CMSMode::Off) {
    BuildCMSTransform();
  }

  return NS_OK;
}

nsresult nsJXLDecoder::EnsureSurfacePipe() {
  if (mCurrentPipe) {
    return NS_OK;
  }

  MOZ_ASSERT(HasSize());
  OrientedIntSize size = Size();
  JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
  MOZ_ASSERT(basicInfo.valid);

  Maybe<AnimationParams> animParams;
  if (HasAnimation()) {
    JxlFrameInfo frameInfo = jxl_decoder_get_frame_info(mDecoder.get());
    MOZ_ASSERT(frameInfo.frame_duration_valid);
    if (!frameInfo.frame_duration_valid) {
      return NS_ERROR_FAILURE;
    }
    animParams.emplace(FullFrame().ToUnknownRect(),
                       FrameTimeout::FromRawMilliseconds(frameInfo.duration_ms),
                       mFrameIndex, BlendMethod::SOURCE, DisposalMethod::KEEP);
  }

  // Cmyk8 with CMS: qcms outputs RGB8 (3 bytes/pixel), pipe handles R8G8B8.
  // All other cases: pipe input is R8G8B8A8.
  SurfaceFormat inFormat;
  SurfaceFormat outFormat;
  if (mPixelFormat.value() == PixelFormat::Cmyk8 && mTransform) {
    inFormat = SurfaceFormat::R8G8B8;
    outFormat = SurfaceFormat::OS_RGBX;
  } else {
    inFormat = SurfaceFormat::R8G8B8A8;
    outFormat =
        basicInfo.has_alpha && mPixelFormat.value() != PixelFormat::Cmyk8
            ? SurfaceFormat::OS_RGBA
            : SurfaceFormat::OS_RGBX;
  }

  // mTransform usage: for Rgba8 it is passed to SurfacePipe for inline CMS;
  // for all other formats it is applied per-row in WritePixelRowsToPipe
  // (Rgba16f via qcms_transform_data_rgba_f16_to_rgba_u8, gray/CMYK via
  // qcms_transform_data). Null when CMS is off, the ICC profile is
  // missing/invalid, or the profile color space doesn't match mPixelFormat.
  bool usePipeTransform = mPixelFormat.value() == PixelFormat::Rgba8;
  qcms_transform* pipeTransform = usePipeTransform ? mTransform : nullptr;

  // jxl-rs always outputs straight alpha; the pipe handles premultiplication
  // for all formats. CMYK is excluded as its pipe input has no alpha channel.
  const bool wantPremultiply =
      !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
  SurfacePipeFlags pipeFlags = SurfacePipeFlags();
  if (wantPremultiply && mPixelFormat.value() != PixelFormat::Cmyk8) {
    pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
  }

  mCurrentPipe = SurfacePipeFactory::CreateSurfacePipe(
      this, size, OutputSize(), FullFrame(), inFormat, outFormat, animParams,
      pipeTransform, pipeFlags);
  if (!mCurrentPipe) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

void nsJXLDecoder::BuildCMSTransform() {
  size_t iccLen = 0;
  const uint8_t* iccData = jxl_decoder_get_icc_profile(mDecoder.get(), &iccLen);
  if (iccData && iccLen) {
    mInProfile = qcms_profile_from_memory(
        reinterpret_cast<const char*>(iccData), iccLen);
    if (mInProfile) {
      auto intent = static_cast<qcms_intent>(gfxPlatform::GetRenderingIntent());
      if (intent < QCMS_INTENT_MIN || intent > QCMS_INTENT_MAX) {
        intent = qcms_profile_get_rendering_intent(mInProfile);
      }

      uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
      qcms_data_type inType;
      qcms_data_type outType;
      bool compatible = true;

      if (profileSpace == icSigGrayData) {
        if (mPixelFormat.value() != PixelFormat::Gray8 &&
            mPixelFormat.value() != PixelFormat::GrayAlpha8) {
          compatible = false;
        }
        // jxl-rs outputs Gray8 or GrayAlpha8; qcms produces Rgba8 output.
        inType = mPixelFormat.value() == PixelFormat::GrayAlpha8
                     ? QCMS_DATA_GRAYA_8
                     : QCMS_DATA_GRAY_8;
        outType = QCMS_DATA_RGBA_8;
      } else if (profileSpace == icSigCmykData) {
        if (mPixelFormat.value() != PixelFormat::Cmyk8) {
          compatible = false;
        }
        // jxl-rs outputs C,M,Y,_ in Rgba8 positions; K in mKBuffer.
        // qcms expects CMYK (0=no ink) and produces RGB8 output.
        inType = QCMS_DATA_CMYK;
        outType = QCMS_DATA_RGB_8;
      } else {
        if (mPixelFormat.value() != PixelFormat::Rgba8 &&
            mPixelFormat.value() != PixelFormat::Rgba16f) {
          compatible = false;
        }
        inType = QCMS_DATA_RGBA_8;
        outType = QCMS_DATA_RGBA_8;
      }

      if (compatible) {
        mTransform = qcms_transform_create(
            mInProfile, inType, GetCMSOutputProfile(), outType, intent);
      }
    }
  }
}

// IEEE 754 half-float to float, used for HDR fallback when no CMS transform.
static float F16ToF32(uint16_t h) {
  uint32_t sign = (h >> 15) & 1u;
  uint32_t exp = (h >> 10) & 0x1fu;
  uint32_t mantissa = h & 0x3ffu;
  uint32_t f;
  if (exp == 0) {
    f = sign << 31;  // zero (subnormals treated as zero)
  } else if (exp == 31) {
    f = (sign << 31) | (0xffu << 23) | (mantissa << 13);  // inf / nan
  } else {
    f = (sign << 31) | ((exp + 127u - 15u) << 23) | (mantissa << 13);
  }
  float result;
  memcpy(&result, &f, sizeof(result));
  return result;
}

bool nsJXLDecoder::WritePixelRowsToPipe() {
  MOZ_ASSERT(mCurrentPipe);
#ifdef DEBUG
  ++mWritePixelRowsCount;
#endif
  OrientedIntSize size = Size();

  uint8_t* currentRow = mPixelBuffer.begin();
  for (int32_t y = 0; y < size.height; ++y) {
    uint8_t* pipeInput;
    if (mPixelFormat.value() == PixelFormat::Rgba16f) {
      if (mTransform) {
        qcms_transform_data_rgba_f16_to_rgba_u8(
            mTransform, reinterpret_cast<const uint16_t*>(currentRow),
            mU8RowBuf.begin(), size.width);
      } else {
        // No CMS: clip f16 to [0,1].
        const uint16_t* src = reinterpret_cast<const uint16_t*>(currentRow);
        for (int32_t i = 0; i < size.width * 4; ++i) {
          float v = F16ToF32(src[i]);
          mU8RowBuf[i] =
              v <= 0.0f ? 0 : (v >= 1.0f ? 255 : uint8_t(v * 255.0f + 0.5f));
        }
      }
      pipeInput = mU8RowBuf.begin();
    } else if (mPixelFormat.value() == PixelFormat::Gray8 ||
               mPixelFormat.value() == PixelFormat::GrayAlpha8) {
      if (mTransform) {
        // qcms reads the packed Gray8/GrayAlpha8 and produces Rgba8 output.
        qcms_transform_data(mTransform, currentRow, mU8RowBuf.begin(),
                            size.width);
      } else {
        // No CMS: expand gray → Rgba8 without color management.
        uint8_t* out = mU8RowBuf.begin();
        for (int32_t x = 0; x < size.width; ++x) {
          uint8_t g = currentRow[x * BytesPerPixel()];
          uint8_t a = mPixelFormat.value() == PixelFormat::GrayAlpha8
                          ? currentRow[x * BytesPerPixel() + 1]
                          : 255;
          out[x * 4] = g;
          out[x * 4 + 1] = g;
          out[x * 4 + 2] = g;
          out[x * 4 + 3] = a;
        }
      }
      pipeInput = mU8RowBuf.begin();
    } else if (mPixelFormat.value() == PixelFormat::Cmyk8) {
      uint8_t* out = mU8RowBuf.begin();
      const uint8_t* kRow =
          mKBuffer.empty() ? nullptr : mKBuffer.begin() + y * size.width;
      if (mTransform) {
        // JXL CMYK: all channels use 0=max-ink, 255=no-ink; qcms uses 0=no-ink,
        // so invert all. qcms produces RGB8 (3 bytes/pixel); pipe was
        // configured with R8G8B8 inFormat.
        for (int32_t x = 0; x < size.width; ++x) {
          out[x * 4] = 255 - currentRow[x * 4];
          out[x * 4 + 1] = 255 - currentRow[x * 4 + 1];
          out[x * 4 + 2] = 255 - currentRow[x * 4 + 2];
          // Alpha from the pixel buffer is unused; K fills the 4th CMYK slot.
          out[x * 4 + 3] = kRow ? (255 - kRow[x]) : 0;
        }
        qcms_transform_data(mTransform, out, out, size.width);
      } else {
        // No CMS: naive CMY+K → RGB without color management.
        // JXL encodes 0=max-ink, 255=no-ink, so R = C*K/255 gives correct
        // luminance.
        for (int32_t x = 0; x < size.width; ++x) {
          uint8_t k = kRow ? kRow[x] : 255;
          out[x * 4] = (uint16_t)currentRow[x * 4] * k / 255;
          out[x * 4 + 1] = (uint16_t)currentRow[x * 4 + 1] * k / 255;
          out[x * 4 + 2] = (uint16_t)currentRow[x * 4 + 2] * k / 255;
          out[x * 4 + 3] = 255;
        }
      }
      pipeInput = out;
    } else {
      pipeInput = currentRow;
    }
    if (mCurrentPipe->WriteBuffer(reinterpret_cast<uint32_t*>(pipeInput)) ==
        WriteState::FAILURE) {
      return false;
    }
    currentRow += size.width * BytesPerPixel();
  }
  return true;
}

nsresult nsJXLDecoder::FinishFrame() {
  MOZ_ASSERT(HasSize());
  MOZ_ASSERT(mDecoder);

  nsresult rv = EnsureSurfacePipe();
  if (NS_FAILED(rv)) {
    return rv;
  }

  JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());

  mCurrentPipe->ResetToFirstRow();
  if (!WritePixelRowsToPipe()) {
    mCurrentPipe.reset();
    return NS_ERROR_FAILURE;
  }

  if (Maybe<SurfaceInvalidRect> invalidRect = mCurrentPipe->TakeInvalidRect()) {
    PostInvalidation(invalidRect->mInputSpaceRect,
                     Some(invalidRect->mOutputSpaceRect));
  }

  // Cmyk8 images are always opaque: alpha is not decoded (see
  // WritePixelRowsToPipe).
  bool hasTransparency =
      basicInfo.has_alpha && mPixelFormat.value() != PixelFormat::Cmyk8;
  PostFrameStop(hasTransparency ? Opacity::SOME_TRANSPARENCY
                                : Opacity::FULLY_OPAQUE);
  mCurrentPipe.reset();
  return NS_OK;
}

void nsJXLDecoder::FlushPartialFrame() {
  MOZ_ASSERT(!mPixelBuffer.empty());

  JxlDecoderStatus status = jxl_decoder_flush_pixels(
      mDecoder.get(), mPixelBuffer.begin(), mPixelBuffer.length(),
      mKBuffer.empty() ? nullptr : mKBuffer.begin(), mKBuffer.length());
  if (status != JxlDecoderStatus::Ok) {
    // Nothing new was rendered.
    return;
  }

  // Lazily create the SurfacePipe now that we have content for it. Doing
  // this before any pixels are ready would expose an opaque-black surface
  // for images without an alpha channel.
  if (NS_FAILED(EnsureSurfacePipe())) {
    MOZ_LOG(sJXLLog, LogLevel::Error,
            ("[this=%p] nsJXLDecoder::FlushPartialFrame -- "
             "EnsureSurfacePipe failed\n",
             this));
    return;
  }

  mCurrentPipe->ResetToFirstRow();
  WritePixelRowsToPipe();

  if (Maybe<SurfaceInvalidRect> invalidRect = mCurrentPipe->TakeInvalidRect()) {
    PostInvalidation(invalidRect->mInputSpaceRect,
                     Some(invalidRect->mOutputSpaceRect));
  }
}

}  // namespace mozilla::image

Messung V0.5 in Prozent
C=89 H=94 G=91

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-09-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126438
#Domains=1867298