diff --git a/service/imaging/processor_default.go b/service/imaging/processor_default.go index 7fec2d26e..f93a4f039 100644 --- a/service/imaging/processor_default.go +++ b/service/imaging/processor_default.go @@ -119,7 +119,6 @@ func (p *defaultProcessor) FitAnimationGIF(src io.Reader, width, height int) (*b var ( gifBound = image.Rect(0, 0, srcWidth, srcHeight) - // フレームを重ねるためのキャンバス // 差分最適化されたGIFに対応するための処置 // 差分最適化されたGIFでは、1フレーム目以外、周りが透明ピクセルのフレームを @@ -128,8 +127,10 @@ func (p *defaultProcessor) FitAnimationGIF(src io.Reader, width, height int) (*b // 混ざった色が透明色ではなくなってフレームの縁に黒っぽいノイズが入ってしまう // ため、キャンバスでフレームを重ねてから縮小する tempCanvas = image.NewNRGBA(gifBound) - // DisposalPreviousに対応するため、Disposeされていないフレームを保持 - unDisposedFrame = image.NewNRGBA(gifBound) + // DisposalBackgroundに対応するための、背景色の画像 + bgColorUniform = image.NewUniform(srcImage.Config.ColorModel.(color.Palette)[srcImage.BackgroundIndex]) + // DisposalPreviousに対応するため、直前のフレームを保持するためのキャンバス + backupCanvas = image.NewNRGBA(gifBound) // destImage.ImageのためのMutex destImageMutex = &sync.Mutex{} @@ -148,28 +149,18 @@ func (p *defaultProcessor) FitAnimationGIF(src io.Reader, width, height int) (*b int(math.Round(float64(srcBounds.Max.Y)*ratio)), ) - switch srcImage.Disposal[i] { - case gif.DisposalBackground: - // Disposalが2に設定されていたらキャンバスを初期化 - tempCanvas = image.NewNRGBA(gifBound) - - case gif.DisposalPrevious: - // Disposalが3に設定されていたら、Disposeされていないフレームまでキャンバスを戻す - tempCanvas = unDisposedFrame - } - - // キャンバスに読んだフレームを重ねる - draw.Draw(tempCanvas, srcBounds, srcFrame, srcBounds.Min, draw.Over) - - // Disposalが1に設定されていたら、Disposeされていないフレームを更新 - if srcImage.Disposal[i] == gif.DisposalNone { - unDisposedFrame = &image.NRGBA{ + // DisposalがPreviousなら、今のキャンバスをDeep Copyしてバックアップ + if srcImage.Disposal[i] == gif.DisposalPrevious { + backupCanvas = &image.NRGBA{ Pix: append([]uint8{}, tempCanvas.Pix...), Stride: tempCanvas.Stride, Rect: tempCanvas.Rect, - } // tempCanvasはポインタを使い回しているので、Deep Copyする + } } + // キャンバスに読んだフレームを重ねる + draw.Draw(tempCanvas, srcBounds, srcFrame, srcBounds.Min, draw.Over) + // 拡縮用GoRoutineを起動 eg.Go(resizeRoutine(frameData{ index: i, @@ -184,6 +175,19 @@ func (p *defaultProcessor) FitAnimationGIF(src io.Reader, width, height int) (*b destBounds: destBounds, srcPalette: srcImage.Image[i].Palette, }, destImage, destImageMutex)) + + switch srcImage.Disposal[i] { + case gif.DisposalBackground: // DisposalがBackgroundなら、このフレームの範囲を背景色で塗りつぶす + // フレームのカラーパレットに透明色が含まれていたら、背景色を透明色とみなす + r, g, b, a := srcFrame.Palette[srcFrame.Palette.Index(color.Transparent)].RGBA() + if r == 0 && g == 0 && b == 0 && a == 0 { + draw.Draw(tempCanvas, srcBounds, image.Transparent, image.Point{}, draw.Src) + } else { + draw.Draw(tempCanvas, srcBounds, bgColorUniform, image.Point{}, draw.Src) + } + case gif.DisposalPrevious: // DisposalがPreviousなら、直前のフレームを復元 + tempCanvas = backupCanvas + } } err = eg.Wait() diff --git a/service/imaging/processor_default_test.go b/service/imaging/processor_default_test.go index 8d79d3e61..94552c965 100644 --- a/service/imaging/processor_default_test.go +++ b/service/imaging/processor_default_test.go @@ -92,7 +92,13 @@ func TestProcessorDefault_FitAnimationGIF(t *testing.T) { err: ErrInvalidImageSrc, }, { - name: "success (tooth 正方形、Disposal設定アリ)", + name: "success (mushroom 正方形、小サイズ)", + file: "mushroom.gif", + want: lo.Must(io.ReadAll(testutils.MustOpenGif("mushroom_resized.gif"))), + err: nil, + }, + { + name: "success (tooth 正方形、DisposalBackground)", file: "tooth.gif", want: lo.Must(io.ReadAll(testutils.MustOpenGif("tooth_resized.gif"))), err: nil, @@ -109,6 +115,12 @@ func TestProcessorDefault_FitAnimationGIF(t *testing.T) { want: lo.Must(io.ReadAll(testutils.MustOpenGif("miku_resized.gif"))), err: nil, }, + { + name: "success (frog 縦長、DisposalBackground + 背景色不整合)", + file: "frog.gif", + want: lo.Must(io.ReadAll(testutils.MustOpenGif("frog_resized.gif"))), + err: nil, + }, } for _, tt := range test { diff --git a/testdata/gif/embed.go b/testdata/gif/embed.go index ac20df2ac..c3c37f02d 100644 --- a/testdata/gif/embed.go +++ b/testdata/gif/embed.go @@ -9,9 +9,14 @@ var FS embed.FS // GIF画像 出典 +// frog.gif https://sozai-good.com/illust/gifanimation/29065 + // miku.gif https://piapro.jp/t/FB3J // marucaさんの作品 +// mushroom.gif http://www.ugokue.com +// 【動け!!動く絵】様より + // new_year.gif https://freesozaixtrain.web.fc2.com/freesozai-nenga-train2.html // tooth.gif https://patirabi.com/2021/10/10/061gif/ diff --git a/testdata/gif/frog.gif b/testdata/gif/frog.gif new file mode 100644 index 000000000..a5b00e4b6 Binary files /dev/null and b/testdata/gif/frog.gif differ diff --git a/testdata/gif/frog_resized.gif b/testdata/gif/frog_resized.gif new file mode 100644 index 000000000..dcd5bcbb0 Binary files /dev/null and b/testdata/gif/frog_resized.gif differ diff --git a/testdata/gif/mushroom.gif b/testdata/gif/mushroom.gif new file mode 100644 index 000000000..43f4555c2 Binary files /dev/null and b/testdata/gif/mushroom.gif differ diff --git a/testdata/gif/mushroom_resized.gif b/testdata/gif/mushroom_resized.gif new file mode 100644 index 000000000..07218ace7 Binary files /dev/null and b/testdata/gif/mushroom_resized.gif differ