Genshin/phantom_svg

View on GitHub
lib/phantom/parser/png_reader.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'rapngasm'
require 'rsvg2'
require 'tmpdir'
require 'rexml/document'

require_relative '../frame.rb'
require_relative 'abstract_image_reader.rb'

module Phantom
  module SVG
    module Parser
      # PNG reader.
      class PNGReader < AbstractImageReader
        # Read png file from path.
        def read(path, _options = {})
          reset

          return if path.nil? || path.empty?

          apngasm = APNG::APNGAsm.new
          apngasm.disassemble(path)

          if apngasm.frame_count == 1
            read_png(path)
          else
            read_apng(apngasm)
          end
        end

        private

        def read_png(path)
          frame = create_frame(path)
          @frames << frame
          @width = "#{frame.width}"
          @height = "#{frame.height}"
        end

        def read_apng(apngasm)
          @width = @height = 0
          Dir.mktmpdir(nil, File.dirname(__FILE__)) do |dir|
            set_frame(apngasm, dir)
          end

          @width = "#{@width}px"
          @height = "#{@height}px"
          @loops = apngasm.get_loops
          @skip_first = apngasm.is_skip_first
          @has_animation = true
        end

        def set_frame(apngasm, dir)
          # Create temporary file.
          apngasm.save_pngs(dir)

          # Create frames.
          apngasm.get_frames.each_with_index do |png_frame, index|
            @width = png_frame.width if @width < png_frame.width
            @height = png_frame.height if @height < png_frame.height
            duration = png_frame.delay_numerator.to_f / png_frame.delay_denominator.to_f
            @frames << create_frame("#{dir}/#{index}.png", duration)
          end
        end

        # Create frame.
        def create_frame(path, duration = nil)
          pixbuf = GdkPixbuf::Pixbuf.new(file: path)
          frame = set_param(path, pixbuf, duration)

          frame
        end

        def set_param(path, pixbuf, duration)
          frame = Phantom::SVG::Frame.new
          frame.width = "#{pixbuf.width}px"
          frame.height = "#{pixbuf.height}px"
          frame.viewbox.set_from_text("0 0 #{pixbuf.width} #{pixbuf.height}")
          frame.surfaces = create_surfaces(path, pixbuf.width, pixbuf.height)
          frame.duration = duration unless duration.nil?
          frame.namespaces = { 'xmlns' => 'http://www.w3.org/2000/svg',
                               'xlink' => 'http://www.w3.org/1999/xlink' }
          frame
        end

        # Create surfaces.
        def create_surfaces(path, width, height)
          bin = File.binread(path)
          base64 = [bin].pack('m')

          image = REXML::Element.new('image')
          image.add_attributes(
            'width' => width,
            'height' => height,
            'xlink:href' => "data:image/png;base64,#{base64}"
          )

          [image]
        end
      end # class PNGReader
    end # Parser
  end # SVG
end # Phantom