aboutsummaryrefslogtreecommitdiffstats
path: root/core/mui/gtk_photo_pixbuf.rb
blob: 34e7c64789e99958c548bbd782f65e918ca7c80c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# -*- coding: utf-8 -*-
miquire :lib, 'retriever/mixin/photo_mixin'

module Retriever::PhotoMixin

  GdkPixbufCache = Struct.new(:pixbuf, :width, :height, :read_count, :reserver)

  # 特定のサイズのPixbufを作成するDeferredを返す
  def download_pixbuf(width:, height:)
    cache_get_defer(width: width, height: height).trap do |err|
      error err if err
      gen_pixbuf_from_raw_data(width: width, height: height)
    end
  end

  private

  def gen_pixbuf_from_raw_data(width:, height:)
    download.next{|photo|
      pb = pixbuf_cache_get(width: width, height: height)
      if pb
        pb
      else
        loader = Gdk::PixbufLoader.new
        loader.write photo.blob
        loader.close
        pb = loader.pixbuf
        pixbuf_cache_set(pb.scale(*calc_fitclop(pb, Gdk::Rectangle.new(0, 0, width, height))),
                  width: width,
                  height: height)
      end
    }
  end

  def cache_get_defer(width:, height:)
    Deferred.new.next do
      result = pixbuf_cache_get(width: width, height: height)
      if result
        result
      else
        Deferred.fail(result)
      end
    end
  end

  def pixbuf_cache_get(width:, height:)
    result = pixbuf_cache[[width, height].hash]
    if result
      result.read_count += 1
      result.reserver.cancel
      result.reserver = pixbuf_forget([width, height].hash, result.read_count)
      result.pixbuf
    end
  end

  def pixbuf_cache_set(pixbuf, width:, height:)
    if pixbuf_cache[[width, height].hash]
      error "cache already exists for #{uri} #{width}*#{height}"
      pixbuf_cache_get(width: width, height: height)
    else
      key = [width, height].hash
      pixbuf_cache[key] =
        GdkPixbufCache.new(pixbuf, width, height, 0, pixbuf_forget(key, 0))
      pixbuf
    end
  end

  def pixbuf_cache
    @pixbuf_cache ||= Hash.new
  end

  def pixbuf_forget(key, gen)
    Reserver.new([300, 60 * gen ** 2].max) do
      pixbuf_cache.delete(key)
    end
  end

  # _src_ が _rect_ にアスペクト比を維持した状態で内接するように縮小した場合のサイズを返す
  # ==== Args
  # [src] 元の寸法(Gtk::Rectangle)
  # [dst] 収めたい枠の寸法(Gtk::Rectangle)
  # ==== Return
  # 幅(px), 高さ(px)
  def calc_fitclop(src, dst)
    if (dst.width * src.height) > (dst.height * src.width)
      return src.width * dst.height / src.height, dst.height
    else
      return dst.width, src.height * dst.width / src.width
    end
  end
end