part of dart._engine;
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Uses image codecs supplied by the CanvasKit WASM bundle.
///
/// See also:
///
///  * `image_web_codecs.dart`, which uses the `ImageDecoder` supplied by the browser.




/// The CanvasKit implementation of [ui.Codec].
///
/// Wraps `SkAnimatedImage`.
class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
    implements ui.Codec {
  /// Decodes an image from a list of encoded bytes.
  CkAnimatedImage.decodeFromBytes(this._bytes, this.src);

  final String src;
  final Uint8List _bytes;
  int _frameCount = 0;
  int _repetitionCount = -1;

  /// Current frame index.
  int _currentFrameIndex = 0;

  @override
  SkAnimatedImage createDefault() {
    final SkAnimatedImage? animatedImage =
        canvasKit.MakeAnimatedImageFromEncoded(_bytes);
    if (animatedImage == null) {
      throw ImageCodecException(
        'Failed to decode image data.\n'
        'Image source: $src',
      );
    }

    _frameCount = animatedImage.getFrameCount();
    _repetitionCount = animatedImage.getRepetitionCount();

    // Normally CanvasKit initializes `SkAnimatedImage` to point to the first
    // frame in the animation. However, if the Skia object has been deleted then
    // resurrected, the framework/app may already have advanced to one of the
    // subsequent frames. When that happens the value of _currentFrameIndex will
    // be something other than zero, and we need to tell the decoder to skip
    // over the previous frames to point to the current one.
    for (int i = 0; i < _currentFrameIndex; i++) {
      animatedImage.decodeNextFrame();
    }

    return animatedImage;
  }

  @override
  SkAnimatedImage resurrect() => createDefault();

  @override
  bool get isResurrectionExpensive => true;

  @override
  void delete() {
    rawSkiaObject?.delete();
  }

  bool _disposed = false;
  bool get debugDisposed => _disposed;

  bool _debugCheckIsNotDisposed() {
    assert(!_disposed, 'This image has been disposed.');
    return true;
  }

  @override
  void dispose() {
    assert(
      !_disposed,
      'Cannot dispose a codec that has already been disposed.',
    );
    _disposed = true;
    delete();
  }

  @override
  int get frameCount {
    assert(_debugCheckIsNotDisposed());
    return _frameCount;
  }

  @override
  int get repetitionCount {
    assert(_debugCheckIsNotDisposed());
    return _repetitionCount;
  }

  @override
  Future<ui.FrameInfo> getNextFrame() {
    assert(_debugCheckIsNotDisposed());
    final SkAnimatedImage animatedImage = skiaObject;

    // SkAnimatedImage comes pre-initialized to point to the current frame (by
    // default the first frame, and, with some special resurrection logic in
    // `createDefault`, to a subsequent frame if resurrection happens in the
    // middle of animation). Flutter's `Codec` semantics is to initialize to
    // point to "just before the first frame", i.e. the first invocation of
    // `getNextFrame` returns the first frame. Therefore, we have to read the
    // current Skia frame, then advance SkAnimatedImage to the next frame, and
    // return the current frame.
    final ui.FrameInfo currentFrame = AnimatedImageFrameInfo(
      Duration(milliseconds: animatedImage.currentFrameDuration()),
      CkImage(animatedImage.makeImageAtCurrentFrame()),
    );

    animatedImage.decodeNextFrame();
    _currentFrameIndex = (_currentFrameIndex + 1) % _frameCount;
    return Future<ui.FrameInfo>.value(currentFrame);
  }
}
