Fix #128: the "previous state" of a "DisposeOpPrevious" frame is its previous frame - for example,

1: APNGDisposeOpNone
2: APNGDisposeOpNone        // should use rendered frame 1
3: APNGDisposeOpPrevious    // should use rendered frame 1
4: APNGDisposeOpPrevious    // should use rendered frame 1, NOT frame 2
5: APNGDisposeOpNone        // should use rendered frame 4
This commit is contained in:
Paddy Xu
2017-11-16 18:53:12 +02:00
parent 0d07315253
commit 734b0e36e3

View File

@@ -38,63 +38,79 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage
var clock = TimeSpan.Zero; var clock = TimeSpan.Zero;
var header = decoder.IHDRChunk; var header = decoder.IHDRChunk;
Frame prevFrame = null; Frame currentFrame = null;
BitmapSource prevRenderedFrame = null; BitmapSource currentRenderedFrame = null;
foreach (var rawFrame in decoder.Frames) BitmapSource previousStateRenderedFrame = null;
foreach (var nextFrame in decoder.Frames)
{ {
var frame = MakeFrame(header, rawFrame, prevFrame, prevRenderedFrame); var nextRenderedFrame = MakeNextFrame(header, nextFrame, currentFrame, currentRenderedFrame, previousStateRenderedFrame);
prevFrame = rawFrame;
prevRenderedFrame = frame;
var delay = TimeSpan.FromSeconds( var delay = TimeSpan.FromSeconds(
(double) rawFrame.fcTLChunk.DelayNum / (double)nextFrame.fcTLChunk.DelayNum /
(rawFrame.fcTLChunk.DelayDen == 0 ? 100 : rawFrame.fcTLChunk.DelayDen)); (nextFrame.fcTLChunk.DelayDen == 0 ? 100 : nextFrame.fcTLChunk.DelayDen));
animator.KeyFrames.Add(new DiscreteObjectKeyFrame(frame, clock)); animator.KeyFrames.Add(new DiscreteObjectKeyFrame(nextRenderedFrame, clock));
clock += delay; clock += delay;
// the "previous state" of a "DisposeOpPrevious" frame is its previous frame, so we do not record it
if (currentFrame != null && currentFrame.fcTLChunk.DisposeOp != DisposeOps.APNGDisposeOpPrevious)
previousStateRenderedFrame = currentRenderedFrame;
currentRenderedFrame = nextRenderedFrame;
currentFrame = nextFrame;
} }
animator.Duration = clock; animator.Duration = clock;
animator.RepeatBehavior = RepeatBehavior.Forever; animator.RepeatBehavior = RepeatBehavior.Forever;
} }
private static BitmapSource MakeFrame(IHDRChunk header, Frame rawFrame, Frame previousFrame, private static BitmapSource MakeNextFrame(IHDRChunk header, Frame nextFrame, Frame currentFrame,
BitmapSource previousRenderedFrame) BitmapSource currentRenderedFrame, BitmapSource previousStateRenderedFrame)
{ {
var fs = rawFrame.GetBitmapSource(); var fullRect = new Rect(0, 0, header.Width, header.Height);
var frameRect = new Rect(nextFrame.fcTLChunk.XOffset, nextFrame.fcTLChunk.YOffset,
nextFrame.fcTLChunk.Width, nextFrame.fcTLChunk.Height);
var fs = nextFrame.GetBitmapSource();
var visual = new DrawingVisual(); var visual = new DrawingVisual();
using (var context = visual.RenderOpen()) using (var context = visual.RenderOpen())
{ {
switch (rawFrame.fcTLChunk.DisposeOp) // protect region
if (nextFrame.fcTLChunk.BlendOp == BlendOps.APNGBlendOpSource)
{ {
case DisposeOps.APNGDisposeOpNone: var freeRegion = new CombinedGeometry(GeometryCombineMode.Xor,
// restore previousRenderedFrame new RectangleGeometry(fullRect),
//if (previousRenderedFrame != null) new RectangleGeometry(frameRect));
//{ context.PushOpacityMask(new DrawingBrush(new GeometryDrawing(Brushes.Transparent, null, freeRegion)));
// var fullRect = new Rect(0, 0, header.Width, header.Height);
// context.DrawImage(previousRenderedFrame, fullRect);
//}
break;
case DisposeOps.APNGDisposeOpPrevious:
// restore previousFrame
if (previousFrame != null)
{
var pFrameRect = new Rect(previousFrame.fcTLChunk.XOffset,
previousFrame.fcTLChunk.YOffset,
previousFrame.fcTLChunk.Width, previousFrame.fcTLChunk.Height);
context.DrawImage(previousFrame.GetBitmapSource(), pFrameRect);
}
break;
case DisposeOps.APNGDisposeOpBackground:
// do nothing
break;
} }
// draw current frame if (currentFrame != null && currentRenderedFrame != null)
var frameRect = new Rect(rawFrame.fcTLChunk.XOffset, rawFrame.fcTLChunk.YOffset, {
rawFrame.fcTLChunk.Width, rawFrame.fcTLChunk.Height); switch (currentFrame.fcTLChunk.DisposeOp)
{
case DisposeOps.APNGDisposeOpNone:
// restore currentRenderedFrame
if (currentRenderedFrame != null)
{
context.DrawImage(currentRenderedFrame, fullRect);
}
break;
case DisposeOps.APNGDisposeOpPrevious:
// restore previousStateRenderedFrame
if (previousStateRenderedFrame != null)
{
context.DrawImage(previousStateRenderedFrame, fullRect);
}
break;
case DisposeOps.APNGDisposeOpBackground:
// do nothing
break;
}
}
// unprotect region and draw the next frame
if (nextFrame.fcTLChunk.BlendOp == BlendOps.APNGBlendOpSource)
context.Pop();
context.DrawImage(fs, frameRect); context.DrawImage(fs, frameRect);
} }
var bitmap = new RenderTargetBitmap( var bitmap = new RenderTargetBitmap(