๐Ÿ–ผ๏ธ [Flutter] GIF ์ด๋ฏธ์ง€ ์ถ”์ถœํ•˜๊ธฐ

Tygerยท2024๋…„ 5์›” 25์ผ
1

Flutter

๋ชฉ๋ก ๋ณด๊ธฐ
60/64
post-custom-banner

๐Ÿ–ผ๏ธ GIF ์ด๋ฏธ์ง€ ์ถ”์ถœํ•˜๊ธฐ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” GIF์˜ ์ด๋ฏธ์ง€๋ฅผ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

GIF๋Š” ์—ฌ๋Ÿฌ์žฅ์˜ ์ด๋ฏธ์ง€๋ฅผ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋กœ ์ „ํ™˜ํ•˜๋ฉด์„œ ๋…ธ์ถœ๋˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์˜๋ฏธํ•˜๋Š”๋ฐ, ์ œํ•œ๋œ ์ด๋ฏธ์ง€ ๊ณต๊ฐ„์— ๋” ๋งŽ์€ ์ปจํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ ์ž ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

Flutter์—์„œ๋Š” GIF์ด๋ฏธ์ง€๋„ ์ผ๋ฐ˜ .jpg, .png ์ด๋ฏธ์ง€์™€ ๋™์ผํ•˜๊ฒŒ Image ์œ„์ ฏ์„ ์‚ฌ์šฉํ•ด์„œ ๋…ธ์ถœ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

GIF๋ฅผ ๋…ธ์ถœ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์€ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•ด ์ค„ ๊ฒƒ์ด ์—†์–ด ๋‹จ์ˆœํ•˜๊ฒŒ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•œ๋ฐ, ๋งŒ์ผ GIF ์ด๋ฏธ์ง€ ์ค‘ ํŠน์ • ์ด๋ฏธ์ง€๋งŒ์„ ๋…ธ์ถœ ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด ์ง€๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค.

๋ฌผ๋ก  ์—ฌ๋Ÿฌ ํŒจํ‚ค์ง€๋“ค์ด ์žˆ์ง€๋งŒ, ํŒจํ‚ค์ง€ ์ง€์›์ด ์›ํ™œํ•œ ์ˆ˜์ค€์€ ์•„๋‹ˆ์—ฌ์„œ ๊ฒฐ๊ตญ ์ง์ ‘ ์ œ์–ดํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ์œ ์ง€ ๋ณด์ˆ˜ ๋ฐ ๊ด€๋ฆฌํ•˜๊ธฐ ์ˆ˜์›”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

Show GIF

๋จผ์ € GIF๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๋„๋ก ํ•˜์ž.

6๊ฐœ์˜ ์ด๋ฏธ์ง€์˜ GIF๋ฅผ ์ค€๋น„ํ•ด์„œ Flutter์—์„œ ๋…ธ์ถœ์‹œ์ผœ ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

GIF ์ด๋ฏธ์ง€๋ฅผ ๋…ธ์ถœ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ผ๋ฐ˜ ์ด๋ฏธ์ง€์™€ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Image.network(
	"https://velog.velcdn.com/images/tygerhwang/post/b3952b7b-a9e8-4c61-b692-76c96c3d4d9f/image.gif",
          ),
GIF Image Flutter

GIF to Images

์˜ˆ์ œ๋กœ ์‚ฌ์šฉํ•œ GIF ์ด๋ฏธ์ง€๋Š” ์ด 6์žฅ์˜ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค์–ด ์กŒ๋‹ค.

์ด๋ฒˆ์—” Flutter์—์„œ ํ•˜๋‚˜์˜ GIF ์ด๋ฏธ์ง€์— ์žˆ๋Š” ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋“ค์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

GIF ์ด๋ฏธ์ง€๋ฅผ ์ถ”์ถœํ•˜๋Š” ํŒจํ‚ค์ง€๋Š” ๋งŽ์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋Š” ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ค.

์šฐ์„  GIF ์ด๋ฏธ์ง€๋Š” ๋„คํŠธ์›Œํฌ ์ƒ์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€์ด๊ธฐ ๋•Œ๋ฌธ์—, GIF์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋ฐ›๊ธฐ ์œ„ํ•œ http ํŒจํ‚ค์ง€๊ฐ€ ํ•„์š”ํ•˜๊ณ , ๋‹ค์šด ๋ฐ›์€ ์ด๋ฏธ์ง€๋ฅผ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋กœ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ €์žฅ์†Œ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” path_provider ํŒจํ‚ค์ง€๋„ ํ•„์š”ํ•˜๋‹ค.

add dependencies

dependencies:
  http: ^1.2.0
  path_provider: ^2.1.3

์ด์ œ GIF ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋ฐ›์•„ ๋ฐ”์ดํŠธ ๋ฐฐ์—ด์„ ๋””์ฝ”๋”ฉํ•˜๊ณ , Codec ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ ํ”„๋ ˆ์ž„ ๋ณ„๋กœ PNG ํŒŒ์ผ๋กœ ์ „ํ™”ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

๋จผ์ € GIF ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋ฐ›์•„ ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ๊ฐ€์ ธ์˜ค๋„๋ก ํ•˜์ž.

import 'package:http/http.dart' as http;

Future<void> _extractFrameAsPng() async {
	final http.Response response = await http.get(Uri.parse(url));
    final Uint8List gifBytes = response.bodyBytes;
}

๋ฐ”์ดํŠธ ๋ฐฐ์—ด์„ dart:ui ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด Codec ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

Codec ๊ฐ์ฒด๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ด๋ฏธ์ง€๋ฅผ ๋””์ฝ”๋”ฉํ•˜๊ณ  ํ”„๋ ˆ์ž„๋ณ„๋กœ ์ฒ˜๋ฆฌํ•ด ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด์ด๋‹ค.

import 'dart:ui' as ui;

Future<void> _extractFrameAsPng() async {
	final ui.Codec codec = await ui.instantiateImageCodec(gifBytes);
    final int frameCount = codec.frameCount;	
}

Codec ๊ฐ์ฒด์˜ ํ”„๋ ˆ์ž„ ๊ฐฏ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐœ์ˆ˜๋งŒํผ ํ”„๋ ˆ์ž„์„ ์ˆœ์ฐจ์ ์œผ๋กœ PNG ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

์ €์žฅ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ์•Œ์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์™€ File ๋ฐ์ดํ„ฐ๋กœ ์ €์žฅ์„ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

Future<void> _extractFrameAsPng() async {
    final directory = await getApplicationDocumentsDirectory();

	for (int i = 0; i < frameCount; i++) {
      	final ui.FrameInfo frameInfo = await codec.getNextFrame();
      	final ui.Image frame = frameInfo.image;
      	final ByteData? byteData =
          await frame.toByteData(format: ui.ImageByteFormat.png);
      	if (byteData != null) {
        	final Uint8List pngBytes = byteData.buffer.asUint8List();
        	final File file = File("${directory.path}/frame_$i.png");
        	await file.writeAsBytes(pngBytes);
      }
    }
}

์ด์ œ ๊ฒฐ๊ณผ๋ฅผ ์‚ดํŽด๋ณด์ž. ์ •์ƒ์ ์œผ๋กœ GIF ์ด๋ฏธ์ง€ ์š”์†Œ๋“ค์„ ์ถ”์ถœํ•˜์˜€๋‹ค.

Code

์ „์ฒด ์ฝ”๋“œ์ด๋‹ค.

  Future<void> _extractFrameAsPng() async {
    final http.Response response = await http.get(Uri.parse(url));
    final Uint8List gifBytes = response.bodyBytes;
    final ui.Codec codec = await ui.instantiateImageCodec(gifBytes);
    final int frameCount = codec.frameCount;
    final directory = await getApplicationDocumentsDirectory();

    for (int i = 0; i < frameCount; i++) {
      final ui.FrameInfo frameInfo = await codec.getNextFrame();
      final ui.Image frame = frameInfo.image;
      final ByteData? byteData =
          await frame.toByteData(format: ui.ImageByteFormat.png);
      if (byteData != null) {
        final Uint8List pngBytes = byteData.buffer.asUint8List();
        final File file = File("${directory.path}/frame_$i.png");
        await file.writeAsBytes(pngBytes);
        images.add(file);
      }
    }
  }

๋งˆ๋ฌด๋ฆฌ

๊ฐ€๋ณ๊ฒŒ GIF์˜ ์ด๋ฏธ์ง€๋ฅผ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด ๋ณด์•˜๋‹ค.

Flutter์—์„œ GIF๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ œ์–ดํ•˜๋Š” ํŒจํ‚ค์ง€๋Š” ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์–ด์„œ ๋ณ„๋„๋กœ ๋‹ค๋ฃจ์ง€๋Š” ์•Š๊ณ , ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ๋‹ค๋ค„๋ณด์•˜๋‹ค.

์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์‚ฌ์‹ค ์—„์ฒญ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•๋“ค์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํŽธํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

profile
Flutter Developer
post-custom-banner

0๊ฐœ์˜ ๋Œ“๊ธ€