blob: 56c7067137ac1744909c8e19a062212ff372440e [file] [log] [blame]
Nigel Taoc19e3b12021-11-11 11:47:08 +11001// Copyright 2021 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//go:build ignore
16// +build ignore
17
18package main
19
20// extract-nia-frames.go extracts the individual frames from a NIA animation,
21// writing them out as NIE images.
22//
23// Usage: go run extract-nia-frames.go foo.nia bar/baz.nia
24//
25// This will write new files:
26// - foo.000000.nie
27// - foo.000001.nie
28// - foo.000002.nie
29// - foo.000003.nie
30// - etc
31// - bar/baz.000000.nie
32// - bar/baz.000001.nie
33// - etc
34
35import (
36 "bufio"
37 "errors"
38 "flag"
39 "fmt"
40 "io"
41 "os"
42 "path/filepath"
43)
44
45var (
46 dryRun = flag.Bool("dry-run", false, "just print metadata; don't write NIE files")
47)
48
49func main() {
50 if err := main1(); err != nil {
51 os.Stderr.WriteString(err.Error() + "\n")
52 os.Exit(1)
53 }
54}
55
56func main1() error {
57 flag.Parse()
58 for _, a := range flag.Args() {
59 if err := extract(a); err != nil {
60 return err
61 }
62 }
63 return nil
64}
65
66func extract(srcFilename string) error {
67 fmt.Printf("Extract %s\n", srcFilename)
68 f, err := os.Open(srcFilename)
69 if err != nil {
70 return err
71 }
72 defer f.Close()
73 r := bufio.NewReader(f)
74
75 dstFilenamePrefix := srcFilename[:len(srcFilename)-len(filepath.Ext(srcFilename))]
76
77 buf := [16]byte{}
78 if _, err := io.ReadFull(r, buf[:16]); err != nil {
79 return err
80 }
81 depth8 := buf[7] == '8'
82 width := u32le(buf[8:])
83 height := u32le(buf[12:])
84 padded := !depth8 && ((width & 1) != 0) && ((height & 1) != 0)
85 if (u32le(buf[:]) != 0x41AF_C36E) || // 0x41AF_C36E is 'nïA'le.
86 (buf[4] != 0xFF) ||
87 (buf[5] != 'b') ||
88 ((buf[6] != 'n') && (buf[6] != 'p')) ||
89 ((buf[7] != '4') && (buf[7] != '8')) ||
90 (width >= 0x8000_0000) ||
91 (height >= 0x8000_0000) {
92 return errors.New("bad NIA header")
93 }
94 fmt.Printf(" Config v1-b%c%c\n", buf[6], buf[7])
95 fmt.Printf(" Width %d\n", width)
96 fmt.Printf(" Height %d\n", height)
97
98 nieSize := calculateNIESize(depth8, width, height)
99 if nieSize < 0 {
100 return errors.New("unsupported NIA (image dimensions are too large)")
101 }
102
103 prevCDD := uint64(0)
104 for frameIndex := uint64(0); ; frameIndex++ {
105 if _, err := io.ReadFull(r, buf[:8]); err != nil {
106 return err
107 }
108 cdd := u64le(buf[:])
109 if (cdd >> 32) == 0x8000_0000 {
110 fmt.Printf(" LoopCount %d\n", uint32(cdd))
111 break
112 } else if ((cdd >> 32) > 0x8000_0000) || (cdd < prevCDD) {
113 return errors.New("bad CDD (Cumulative Display Duration)")
114 }
115 fmt.Printf(" CDD %16d flicks = %8g seconds; delta = %8g seconds\n",
116 cdd,
117 float64(cdd)/705_600000,
118 float64(cdd-prevCDD)/705_600000,
119 )
120 prevCDD = cdd
121
122 dstFilename := fmt.Sprintf("%s.%06d.nie", dstFilenamePrefix, frameIndex)
123 if err := extract1(dstFilename, r, int64(nieSize)); err != nil {
124 return err
125 }
126
127 if padded {
128 if _, err := io.ReadFull(r, buf[:4]); err != nil {
129 return err
130 } else if u32le(buf[:]) != 0 {
131 return errors.New("bad padding")
132 }
133 }
134 }
135
136 return nil
137}
138
139func calculateNIESize(depth8 bool, width uint32, height uint32) int64 {
140 const maxInt64 = (1 << 63) - 1
141 const maxUint64 = (1 << 64) - 1
142
143 n := uint64(width) * uint64(height) * 4
144 if depth8 {
145 if n > (maxUint64 / 2) {
146 return -1
147 }
148 n *= 2
149 }
150 if n > (maxInt64 - 16) {
151 return -1
152 }
153 return int64(n + 16)
154}
155
156func extract1(dstFilename string, r *bufio.Reader, nieSize int64) error {
157 if *dryRun {
158 fmt.Printf(" DryRun %s\n", dstFilename)
159 _, err := io.CopyN(io.Discard, r, nieSize)
160 return err
161 }
162
163 fmt.Printf(" Write %s\n", dstFilename)
164 f, err := os.Create(dstFilename)
165 if err != nil {
166 return err
167 }
168 _, err1 := io.CopyN(f, r, nieSize)
169 err2 := f.Close()
170 if err1 != nil {
171 return err1
172 }
173 return err2
174}
175
176func u32le(b []byte) uint32 {
177 return (uint32(b[0]) << 0) |
178 (uint32(b[1]) << 8) |
179 (uint32(b[2]) << 16) |
180 (uint32(b[3]) << 24)
181}
182
183func u64le(b []byte) uint64 {
184 return (uint64(b[0]) << 0) |
185 (uint64(b[1]) << 8) |
186 (uint64(b[2]) << 16) |
187 (uint64(b[3]) << 24) |
188 (uint64(b[4]) << 32) |
189 (uint64(b[5]) << 40) |
190 (uint64(b[6]) << 48) |
191 (uint64(b[7]) << 56)
192}