| 1 | use nihav_core::codecs::*; |
| 2 | use nihav_core::io::byteio::*; |
| 3 | //use std::str::FromStr; |
| 4 | |
| 5 | struct FrameData { |
| 6 | info: NACodecInfoRef, |
| 7 | width: usize, |
| 8 | height: usize, |
| 9 | frm0: Vec<u16>, |
| 10 | frm1: Vec<u16>, |
| 11 | frm2: Vec<u16>, |
| 12 | } |
| 13 | |
| 14 | impl FrameData { |
| 15 | fn new() -> Self { |
| 16 | Self { |
| 17 | info: NACodecInfoRef::default(), |
| 18 | width: 0, |
| 19 | height: 0, |
| 20 | frm0: Vec::new(), |
| 21 | frm1: Vec::new(), |
| 22 | frm2: Vec::new(), |
| 23 | } |
| 24 | } |
| 25 | fn init(&mut self, info: NACodecInfoRef) -> DecoderResult<()> { |
| 26 | if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { |
| 27 | self.width = vinfo.get_width(); |
| 28 | self.height = vinfo.get_height(); |
| 29 | let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, RGB565_FORMAT)); |
| 30 | self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); |
| 31 | |
| 32 | self.frm0.resize(self.width * self.height, 0); |
| 33 | self.frm1.resize(self.width * self.height, 0); |
| 34 | self.frm2.resize(self.width * self.height, 0); |
| 35 | Ok(()) |
| 36 | } else { |
| 37 | Err(DecoderError::InvalidData) |
| 38 | } |
| 39 | } |
| 40 | fn get_frame(&mut self, pkt: &NAPacket) -> DecoderResult<NAFrameRef> { |
| 41 | let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; |
| 42 | if let Some(ref mut vbuf) = bufinfo.get_vbuf16() { |
| 43 | let stride = vbuf.get_stride(0); |
| 44 | let data = vbuf.get_data_mut().unwrap(); |
| 45 | for (dst, src) in data.chunks_mut(stride).zip(self.frm0.chunks(self.width).take(self.height)) { |
| 46 | dst[..self.width].copy_from_slice(src); |
| 47 | } |
| 48 | } else { |
| 49 | return Err(DecoderError::Bug); |
| 50 | } |
| 51 | |
| 52 | let is_intra = pkt.keyframe; |
| 53 | let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); |
| 54 | frm.set_keyframe(is_intra); |
| 55 | frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P }); |
| 56 | Ok(frm.into_ref()) |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | fn decode_rle(br: &mut ByteReader, dst: &mut [u8]) -> DecoderResult<()> { |
| 61 | let mut len = 0; |
| 62 | let mut clr = 0; |
| 63 | let mut run = false; |
| 64 | |
| 65 | for el in dst.iter_mut() { |
| 66 | if len == 0 { |
| 67 | let op = br.read_byte()?; |
| 68 | run = (op & 1) != 0; |
| 69 | if run { |
| 70 | clr = br.read_byte()?; |
| 71 | } |
| 72 | len = ((op >> 1) + 1) as usize; |
| 73 | } |
| 74 | *el = if run { clr } else { br.read_byte()? }; |
| 75 | len -= 1; |
| 76 | } |
| 77 | validate!(len == 0); |
| 78 | |
| 79 | Ok(()) |
| 80 | } |
| 81 | |
| 82 | struct Smush2Decoder { |
| 83 | glyphs4: [[u8; 16]; 256], |
| 84 | glyphs8: [[u8; 64]; 256], |
| 85 | pic: FrameData, |
| 86 | rle_buf: Vec<u8>, |
| 87 | } |
| 88 | |
| 89 | impl Smush2Decoder { |
| 90 | fn new() -> Self { |
| 91 | let mut glyphs4 = [[0; 16]; 256]; |
| 92 | let mut glyphs8 = [[0; 64]; 256]; |
| 93 | super::make_glyphs_47(&mut glyphs4, &mut glyphs8); |
| 94 | Self { |
| 95 | pic: FrameData::new(), |
| 96 | rle_buf: Vec::new(), |
| 97 | glyphs4, glyphs8, |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | struct BlockData<'a> { |
| 103 | glyphs4: &'a [[u8; 16]; 256], |
| 104 | glyphs8: &'a [[u8; 64]; 256], |
| 105 | frm1: &'a [u16], |
| 106 | frm2: &'a [u16], |
| 107 | cb: &'a [u16; 256], |
| 108 | clr4: [u16; 4], |
| 109 | stride: usize, |
| 110 | } |
| 111 | |
| 112 | fn draw_glyph(dst: &mut [u16], stride: usize, bsize: usize, glyph: &[u8], clr2: [u16; 2]) { |
| 113 | for (dst, src) in dst.chunks_mut(stride).zip(glyph.chunks_exact(bsize)) { |
| 114 | for (el, &bit) in dst[..bsize].iter_mut().zip(src.iter()) { |
| 115 | *el = clr2[bit as usize]; |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | fn do_block2(br: &mut ByteReader, dst: &mut [u16], x: usize, y: usize, bsize: usize, bdata: &BlockData) -> DecoderResult<()> { |
| 121 | let stride = bdata.stride; |
| 122 | let op = br.read_byte()?; |
| 123 | match op { |
| 124 | 0xFF if bsize > 2 => { |
| 125 | let hsize = bsize / 2; |
| 126 | do_block2(br, dst, x, y, hsize, bdata)?; |
| 127 | do_block2(br, &mut dst[hsize..], x + hsize, y, bsize / 2, bdata)?; |
| 128 | do_block2(br, &mut dst[hsize * stride..], x, y + hsize, hsize, bdata)?; |
| 129 | do_block2(br, &mut dst[hsize * (stride + 1)..], x + hsize, y + hsize, bsize / 2, bdata)?; |
| 130 | }, |
| 131 | 0xFF => { |
| 132 | dst[0] = br.read_u16le()?; |
| 133 | dst[1] = br.read_u16le()?; |
| 134 | dst[stride] = br.read_u16le()?; |
| 135 | dst[stride + 1] = br.read_u16le()?; |
| 136 | }, |
| 137 | 0xFE => { |
| 138 | let pix = br.read_u16le()?; |
| 139 | for dst in dst.chunks_mut(stride).take(bsize) { |
| 140 | for el in dst[..bsize].iter_mut() { |
| 141 | *el = pix; |
| 142 | } |
| 143 | } |
| 144 | }, |
| 145 | 0xFD => { |
| 146 | let idx = br.read_byte()? as usize; |
| 147 | let pix = bdata.cb[idx]; |
| 148 | for dst in dst.chunks_mut(stride).take(bsize) { |
| 149 | for el in dst[..bsize].iter_mut() { |
| 150 | *el = pix; |
| 151 | } |
| 152 | } |
| 153 | }, |
| 154 | 0xF9..=0xFC => { |
| 155 | let pix = bdata.clr4[(op - 0xF9) as usize]; |
| 156 | for dst in dst.chunks_mut(stride).take(bsize) { |
| 157 | for el in dst[..bsize].iter_mut() { |
| 158 | *el = pix; |
| 159 | } |
| 160 | } |
| 161 | }, |
| 162 | 0xF8 if bsize > 2 => { |
| 163 | let idx = br.read_byte()? as usize; |
| 164 | let mut clr2 = [0; 2]; |
| 165 | clr2[1] = br.read_u16le()?; |
| 166 | clr2[0] = br.read_u16le()?; |
| 167 | let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; |
| 168 | draw_glyph(dst, stride, bsize, glyph, clr2); |
| 169 | }, |
| 170 | 0xF8 => { |
| 171 | dst[0] = br.read_u16le()?; |
| 172 | dst[1] = br.read_u16le()?; |
| 173 | dst[stride] = br.read_u16le()?; |
| 174 | dst[stride + 1] = br.read_u16le()?; |
| 175 | }, |
| 176 | 0xF7 if bsize > 2 => { |
| 177 | let idx = br.read_byte()? as usize; |
| 178 | let mut clr2 = [0; 2]; |
| 179 | clr2[1] = bdata.cb[br.read_byte()? as usize]; |
| 180 | clr2[0] = bdata.cb[br.read_byte()? as usize]; |
| 181 | let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; |
| 182 | draw_glyph(dst, stride, bsize, glyph, clr2); |
| 183 | }, |
| 184 | 0xF7 => { |
| 185 | dst[0] = bdata.cb[br.read_byte()? as usize]; |
| 186 | dst[1] = bdata.cb[br.read_byte()? as usize]; |
| 187 | dst[stride] = bdata.cb[br.read_byte()? as usize]; |
| 188 | dst[stride + 1] = bdata.cb[br.read_byte()? as usize]; |
| 189 | }, |
| 190 | 0xF6 => { |
| 191 | let off = x + y * stride; |
| 192 | let src = &bdata.frm1[off..]; |
| 193 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { |
| 194 | dst[..bsize].copy_from_slice(&src[..bsize]); |
| 195 | } |
| 196 | }, |
| 197 | 0xF5 => { |
| 198 | let off = br.read_u16le()? as i16 as isize; |
| 199 | let mx = off % (stride as isize); |
| 200 | let my = off / (stride as isize); |
| 201 | let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); |
| 202 | validate!(off >= 0); |
| 203 | let src = &bdata.frm2[off as usize..]; |
| 204 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { |
| 205 | let size = dst.len().min(src.len()).min(bsize); |
| 206 | dst[..size].copy_from_slice(&src[..size]); |
| 207 | } |
| 208 | }, |
| 209 | _ => { |
| 210 | let mx = C47_MV[op as usize][0] as isize; |
| 211 | let my = C47_MV[op as usize][1] as isize; |
| 212 | let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); |
| 213 | let src = &bdata.frm2[off as usize..]; |
| 214 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { |
| 215 | let size = dst.len().min(src.len()).min(bsize); |
| 216 | dst[..size].copy_from_slice(&src[..size]); |
| 217 | } |
| 218 | }, |
| 219 | }; |
| 220 | Ok(()) |
| 221 | } |
| 222 | |
| 223 | impl NADecoder for Smush2Decoder { |
| 224 | fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { |
| 225 | self.pic.init(info) |
| 226 | } |
| 227 | fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> { |
| 228 | let src = pkt.get_buffer(); |
| 229 | validate!(src.len() > 8); |
| 230 | |
| 231 | let mut mr = MemoryReader::new_read(&src); |
| 232 | let mut br = ByteReader::new(&mut mr); |
| 233 | |
| 234 | let mut reorder = 0; |
| 235 | while br.left() > 0 { |
| 236 | let tag = br.read_tag()?; |
| 237 | let size = br.read_u32be()? as usize; |
| 238 | let tend = br.tell() + (size as u64); |
| 239 | validate!((size as i64) <= br.left()); |
| 240 | match &tag { |
| 241 | b"Bl16" => { |
| 242 | validate!(size >= 8); |
| 243 | br.read_skip(2)?; |
| 244 | let _x = br.read_u16le()? as usize; |
| 245 | let _y = br.read_u16le()? as usize; |
| 246 | br.read_skip(2)?; |
| 247 | validate!(_x <= self.pic.width && _y <= self.pic.height); |
| 248 | if size > 8 { |
| 249 | let w = br.read_u32le()? as usize; |
| 250 | let h = br.read_u32le()? as usize; |
| 251 | validate!(w == self.pic.width && h == self.pic.height); |
| 252 | let seq = br.read_u16le()?; |
| 253 | let compr = br.read_byte()?; |
| 254 | reorder = br.read_byte()?; |
| 255 | br.read_skip(4)?; |
| 256 | let mut clr4 = [0; 4]; |
| 257 | for el in clr4.iter_mut() { |
| 258 | *el = br.read_u16le()?; |
| 259 | } |
| 260 | let bg_clr = br.read_u16le()?; |
| 261 | let _fg_clr = br.read_u16le()?; |
| 262 | let _unp_size = br.read_u32le()?; |
| 263 | let mut cb = [0; 256]; |
| 264 | for el in cb.iter_mut() { |
| 265 | *el = br.read_u16le()?; |
| 266 | } |
| 267 | if size > 0x230 { |
| 268 | br.read_skip(8)?; |
| 269 | } |
| 270 | validate!(br.tell() < tend); |
| 271 | let start = br.tell() as usize; |
| 272 | br.seek(SeekFrom::Start(tend))?; |
| 273 | let mut mr = MemoryReader::new_read(&src[start..(tend as usize)]); |
| 274 | let mut br = ByteReader::new(&mut mr); |
| 275 | |
| 276 | if seq == 0 { |
| 277 | for el in self.pic.frm1.iter_mut() { |
| 278 | *el = bg_clr; |
| 279 | } |
| 280 | for el in self.pic.frm2.iter_mut() { |
| 281 | *el = bg_clr; |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | match compr { |
| 286 | 0 => { |
| 287 | for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { |
| 288 | for el in row[..w].iter_mut() { |
| 289 | *el = br.read_u16le()?; |
| 290 | } |
| 291 | } |
| 292 | }, |
| 293 | 1 => { unimplemented!(); }, //decode half-res and interpolate |
| 294 | 2 => { |
| 295 | let bdata = BlockData { |
| 296 | glyphs4: &self.glyphs4, |
| 297 | glyphs8: &self.glyphs8, |
| 298 | frm1: &self.pic.frm1, |
| 299 | frm2: &self.pic.frm2, |
| 300 | stride: self.pic.width, |
| 301 | clr4, |
| 302 | cb: &cb, |
| 303 | }; |
| 304 | let dst = &mut self.pic.frm0; |
| 305 | let stride = self.pic.width; |
| 306 | for (row_no, row) in dst.chunks_mut(stride * 8).take((h + 7) / 8).enumerate() { |
| 307 | for col in (0..w).step_by(8) { |
| 308 | do_block2(&mut br, &mut row[col..], col, row_no * 8, 8, &bdata)?; |
| 309 | } |
| 310 | } |
| 311 | }, |
| 312 | 3 => { |
| 313 | self.pic.frm0.copy_from_slice(&self.pic.frm2); |
| 314 | }, |
| 315 | 4 => { |
| 316 | self.pic.frm0.copy_from_slice(&self.pic.frm1); |
| 317 | }, |
| 318 | 5 => { |
| 319 | let size = w * h * 2; |
| 320 | self.rle_buf.resize(size, 0); |
| 321 | decode_rle(&mut br, &mut self.rle_buf)?; |
| 322 | for (drow, srow) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w * 2)) { |
| 323 | for (dst, src) in drow.iter_mut().zip(srow.chunks_exact(2)) { |
| 324 | *dst = read_u16le(src)?; |
| 325 | } |
| 326 | } |
| 327 | }, |
| 328 | 6 => { |
| 329 | for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { |
| 330 | for el in row[..w].iter_mut() { |
| 331 | let idx = br.read_byte()? as usize; |
| 332 | *el = cb[idx]; |
| 333 | } |
| 334 | } |
| 335 | }, |
| 336 | 7 => { unimplemented!(); }, //decode half-res using codebook indices and interpolate |
| 337 | 8 => { |
| 338 | let size = w * h; |
| 339 | self.rle_buf.resize(size, 0); |
| 340 | decode_rle(&mut br, &mut self.rle_buf)?; |
| 341 | for (row, src) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w)) { |
| 342 | for (el, &idx) in row.iter_mut().zip(src.iter()) { |
| 343 | *el = cb[idx as usize]; |
| 344 | } |
| 345 | } |
| 346 | }, |
| 347 | _ => return Err(DecoderError::NotImplemented), |
| 348 | }; |
| 349 | } |
| 350 | }, |
| 351 | _ => br.read_skip(size)?, |
| 352 | }; |
| 353 | } |
| 354 | |
| 355 | let ret = self.pic.get_frame(pkt); |
| 356 | |
| 357 | if reorder == 2 { |
| 358 | std::mem::swap(&mut self.pic.frm1, &mut self.pic.frm2); |
| 359 | } |
| 360 | if reorder != 0 { |
| 361 | std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); |
| 362 | } |
| 363 | |
| 364 | ret |
| 365 | } |
| 366 | fn flush(&mut self) { |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | impl NAOptionHandler for Smush2Decoder { |
| 371 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } |
| 372 | fn set_options(&mut self, _options: &[NAOption]) { } |
| 373 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } |
| 374 | } |
| 375 | |
| 376 | |
| 377 | pub fn get_decoder_video_v2() -> Box<dyn NADecoder + Send> { |
| 378 | Box::new(Smush2Decoder::new()) |
| 379 | } |
| 380 | |
| 381 | #[cfg(test)] |
| 382 | mod test { |
| 383 | use nihav_core::codecs::RegisteredDecoders; |
| 384 | use nihav_core::demuxers::RegisteredDemuxers; |
| 385 | use nihav_codec_support::test::dec_video::*; |
| 386 | use crate::game_register_all_decoders; |
| 387 | use crate::game_register_all_demuxers; |
| 388 | // sample from Grim Fandango |
| 389 | #[test] |
| 390 | fn test_smush_sanm() { |
| 391 | let mut dmx_reg = RegisteredDemuxers::new(); |
| 392 | game_register_all_demuxers(&mut dmx_reg); |
| 393 | let mut dec_reg = RegisteredDecoders::new(); |
| 394 | game_register_all_decoders(&mut dec_reg); |
| 395 | |
| 396 | // sample from Grim Fandango |
| 397 | test_decoding("smush", "smushv2", "assets/Game/smush/lol.snm", Some(4), &dmx_reg, &dec_reg, |
| 398 | ExpectedTestResult::MD5Frames(vec![ |
| 399 | [0x408e4dc9, 0x4483d7d8, 0xc9fae314, 0x3bb45ec9], |
| 400 | [0x83548952, 0x0b4a6ccb, 0x42609794, 0x59d3c7d4], |
| 401 | [0x5349f6ca, 0x56361199, 0x7194439f, 0x90df21b8], |
| 402 | [0x0c359bab, 0xed69f862, 0x9c899813, 0x3f6aac2a], |
| 403 | [0x58870617, 0x97c5f3a6, 0x1b2c761c, 0x6ec1cd0e]])); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | const C47_MV: [[i8; 2]; 255] = [ |
| 408 | [ 0, 0], [ -1, -43], [ 6, -43], [ -9, -42], [ 13, -41], |
| 409 | [-16, -40], [ 19, -39], [-23, -36], [ 26, -34], [ -2, -33], |
| 410 | [ 4, -33], [-29, -32], [ -9, -32], [ 11, -31], [-16, -29], |
| 411 | [ 32, -29], [ 18, -28], [-34, -26], [-22, -25], [ -1, -25], |
| 412 | [ 3, -25], [ -7, -24], [ 8, -24], [ 24, -23], [ 36, -23], |
| 413 | [-12, -22], [ 13, -21], [-38, -20], [ 0, -20], [-27, -19], |
| 414 | [ -4, -19], [ 4, -19], [-17, -18], [ -8, -17], [ 8, -17], |
| 415 | [ 18, -17], [ 28, -17], [ 39, -17], [-12, -15], [ 12, -15], |
| 416 | [-21, -14], [ -1, -14], [ 1, -14], [-41, -13], [ -5, -13], |
| 417 | [ 5, -13], [ 21, -13], [-31, -12], [-15, -11], [ -8, -11], |
| 418 | [ 8, -11], [ 15, -11], [ -2, -10], [ 1, -10], [ 31, -10], |
| 419 | [-23, -9], [-11, -9], [ -5, -9], [ 4, -9], [ 11, -9], |
| 420 | [ 42, -9], [ 6, -8], [ 24, -8], [-18, -7], [ -7, -7], |
| 421 | [ -3, -7], [ -1, -7], [ 2, -7], [ 18, -7], [-43, -6], |
| 422 | [-13, -6], [ -4, -6], [ 4, -6], [ 8, -6], [-33, -5], |
| 423 | [ -9, -5], [ -2, -5], [ 0, -5], [ 2, -5], [ 5, -5], |
| 424 | [ 13, -5], [-25, -4], [ -6, -4], [ -3, -4], [ 3, -4], |
| 425 | [ 9, -4], [-19, -3], [ -7, -3], [ -4, -3], [ -2, -3], |
| 426 | [ -1, -3], [ 0, -3], [ 1, -3], [ 2, -3], [ 4, -3], |
| 427 | [ 6, -3], [ 33, -3], [-14, -2], [-10, -2], [ -5, -2], |
| 428 | [ -3, -2], [ -2, -2], [ -1, -2], [ 0, -2], [ 1, -2], |
| 429 | [ 2, -2], [ 3, -2], [ 5, -2], [ 7, -2], [ 14, -2], |
| 430 | [ 19, -2], [ 25, -2], [ 43, -2], [ -7, -1], [ -3, -1], |
| 431 | [ -2, -1], [ -1, -1], [ 0, -1], [ 1, -1], [ 2, -1], |
| 432 | [ 3, -1], [ 10, -1], [ -5, 0], [ -3, 0], [ -2, 0], |
| 433 | [ -1, 0], [ 1, 0], [ 2, 0], [ 3, 0], [ 5, 0], |
| 434 | [ 7, 0], [-10, 1], [ -7, 1], [ -3, 1], [ -2, 1], |
| 435 | [ -1, 1], [ 0, 1], [ 1, 1], [ 2, 1], [ 3, 1], |
| 436 | [-43, 2], [-25, 2], [-19, 2], [-14, 2], [ -5, 2], |
| 437 | [ -3, 2], [ -2, 2], [ -1, 2], [ 0, 2], [ 1, 2], |
| 438 | [ 2, 2], [ 3, 2], [ 5, 2], [ 7, 2], [ 10, 2], |
| 439 | [ 14, 2], [-33, 3], [ -6, 3], [ -4, 3], [ -2, 3], |
| 440 | [ -1, 3], [ 0, 3], [ 1, 3], [ 2, 3], [ 4, 3], |
| 441 | [ 19, 3], [ -9, 4], [ -3, 4], [ 3, 4], [ 7, 4], |
| 442 | [ 25, 4], [-13, 5], [ -5, 5], [ -2, 5], [ 0, 5], |
| 443 | [ 2, 5], [ 5, 5], [ 9, 5], [ 33, 5], [ -8, 6], |
| 444 | [ -4, 6], [ 4, 6], [ 13, 6], [ 43, 6], [-18, 7], |
| 445 | [ -2, 7], [ 0, 7], [ 2, 7], [ 7, 7], [ 18, 7], |
| 446 | [-24, 8], [ -6, 8], [-42, 9], [-11, 9], [ -4, 9], |
| 447 | [ 5, 9], [ 11, 9], [ 23, 9], [-31, 10], [ -1, 10], |
| 448 | [ 2, 10], [-15, 11], [ -8, 11], [ 8, 11], [ 15, 11], |
| 449 | [ 31, 12], [-21, 13], [ -5, 13], [ 5, 13], [ 41, 13], |
| 450 | [ -1, 14], [ 1, 14], [ 21, 14], [-12, 15], [ 12, 15], |
| 451 | [-39, 17], [-28, 17], [-18, 17], [ -8, 17], [ 8, 17], |
| 452 | [ 17, 18], [ -4, 19], [ 0, 19], [ 4, 19], [ 27, 19], |
| 453 | [ 38, 20], [-13, 21], [ 12, 22], [-36, 23], [-24, 23], |
| 454 | [ -8, 24], [ 7, 24], [ -3, 25], [ 1, 25], [ 22, 25], |
| 455 | [ 34, 26], [-18, 28], [-32, 29], [ 16, 29], [-11, 31], |
| 456 | [ 9, 32], [ 29, 32], [ -4, 33], [ 2, 33], [-26, 34], |
| 457 | [ 23, 36], [-19, 39], [ 16, 40], [-13, 41], [ 9, 42], |
| 458 | [ -6, 43], [ 1, 43], [ 0, 0], [ 0, 0], [ 0, 0], |
| 459 | ]; |