Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 574a4f5b34 | |||
| 6313515291 | |||
| c3abc515dc | |||
| d9fb1db793 | |||
| c19aec52a3 | |||
| cc383eb5de | |||
| e9d27cb7cc | |||
| 6a0cc560f5 | |||
| ac7b0a2806 | |||
| 71dd94b76f | |||
| a7f748bbb5 | |||
| 4b577b0644 | |||
| d6005ac502 | |||
| e07f6ae46b | |||
| 363c9b01d4 | |||
| 0356d837e0 | |||
| af71952313 | |||
| 789bf60fbd | |||
| bd8f8941bc | |||
| eec5647b4b | |||
| be15092a8a | |||
| 0d49766425 | |||
| 3e0cffdfbc | |||
| c6cb66b42a | |||
| 8cf35dd34d | |||
| 691f3ccc97 | |||
| 2d660efb78 | |||
| a8a5303f7b | |||
| 3b3db58a20 | |||
| bcffc1b1e3 | |||
| 9ba90f1bd5 | |||
| 9686bdca95 | |||
| ad9ee60a5b | |||
| 68252ce102 | |||
| afd776e836 | |||
| cb0ca77c9b | |||
| 4204f05c0b | |||
| c8c34d6c80 | |||
| c37dd49732 | |||
| 0e5dafa424 | |||
| 7bb8f0a0a4 | |||
| 2c8f95b6fd | |||
| d40f307802 | |||
| 28df8f2271 | |||
| 9731fcde26 | |||
| 93b3b5789c | |||
| d2ef991e76 | |||
| ed1870920d | |||
| 0007810984 | |||
| 1c6a2099ab | |||
| df03ce3d44 | |||
| d864b9f734 | |||
| cbedf9a749 | |||
| f4127166ac | |||
| 3cd7efdf5c |
@@ -12,4 +12,6 @@
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
|
||||
*.bundle
|
||||
|
||||
.DS_Store
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# mikunyan
|
||||
|
||||
Library to deserialize Unity AssetBundle files (\*.unity3d) and asset files.
|
||||
A library to deserialize AssetBundle files (\*.unity3d) and asset files of Unity.
|
||||
|
||||
The name "Mikunyan" is derived from [Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
|
||||
|
||||
Ruby-Doc: http://www.rubydoc.info/gems/mikunyan/
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -18,6 +22,14 @@ Or install it yourself as:
|
||||
|
||||
$ gem install mikunyan
|
||||
|
||||
If you want to install development build:
|
||||
|
||||
$ git clone https://github.com/Ishotihadus/mikunyan
|
||||
$ cd mikunyan
|
||||
$ bundle install
|
||||
$ rake build
|
||||
$ gem install -l pkg/mikunyan-3.9.x.gem
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
@@ -25,67 +37,67 @@ Or install it yourself as:
|
||||
```ruby
|
||||
require 'mikunyan'
|
||||
|
||||
# load AssetBundle
|
||||
# load an AssetBundle file
|
||||
bundle = Mikunyan::AssetBundle.file(filename)
|
||||
|
||||
# you can load AssetBundle from blob
|
||||
# you can also load a bundle from blob
|
||||
# bundle = Mikunyan::AssetBundle.load(blob)
|
||||
|
||||
# select asset (normaly only one asset)
|
||||
# select asset (a bundle normally contains only one asset)
|
||||
asset = bundle.assets[0]
|
||||
|
||||
# or you can directly load asset
|
||||
# or you can directly load an asset from an asset file
|
||||
# asset = Mikunyan::Asset.file(filename)
|
||||
|
||||
# object list
|
||||
# get a list of objects
|
||||
list = asset.objects
|
||||
|
||||
# object PathIds
|
||||
# get PathIds of objects
|
||||
path_ids = asset.path_ids
|
||||
|
||||
# object container table (if available)
|
||||
# get an container table of objects (if available)
|
||||
containers = asset.containers
|
||||
|
||||
# load object (Mikunyan::ObjectValue)
|
||||
# load an object (Mikunyan::ObjectValue)
|
||||
obj = asset.parse_object(path_ids[0])
|
||||
|
||||
# simplified structure (based on Hash)
|
||||
# load an object to Ruby data structures
|
||||
obj_hash = asset.parse_object_simple(path_ids[0])
|
||||
|
||||
# hash can be easily serialized to json
|
||||
# a Hash can be serialized to JSON
|
||||
require 'json'
|
||||
obj_hash.to_json
|
||||
```
|
||||
|
||||
### Mikunyan::ObjectValue
|
||||
|
||||
`Mikunyan::ObjectValue` can be 3 types. Value, array and map.
|
||||
`Mikunyan::ObjectValue` can be 3 types: value, array and key-value table.
|
||||
|
||||
```ruby
|
||||
# You can get whether obj is value or not
|
||||
# get whether obj is value or not
|
||||
obj.value?
|
||||
|
||||
# get value
|
||||
# get a value
|
||||
obj.value
|
||||
|
||||
# same as obj.value
|
||||
obj[]
|
||||
|
||||
|
||||
# You can get whether obj is array or not
|
||||
# get whether obj is array or not
|
||||
obj.array?
|
||||
|
||||
# get array
|
||||
# get an array
|
||||
obj.value
|
||||
|
||||
# you can directly access by index
|
||||
obj[0]
|
||||
|
||||
|
||||
# If obj is map, you can get keys
|
||||
# get keys (if obj is key-value table)
|
||||
obj.keys
|
||||
|
||||
# get child object
|
||||
# get child objects
|
||||
obj[key]
|
||||
|
||||
# same as obj[key]
|
||||
@@ -94,9 +106,9 @@ obj.key
|
||||
|
||||
### Unpack Texture2D
|
||||
|
||||
You can get png file directly from Texture2D asset. Output object's class is `ChunkyPNG::Image`.
|
||||
You can get image data directly from Texture2D object. Output object's class is `ChunkyPNG::Image`.
|
||||
|
||||
Acceptable format is basic texture formats (1, 2, 3, 4, 5, 7 and 13) and ETC_RGB4 (34).
|
||||
Some basic texture formats (1–5, 7, 9, 13–20, 22, 62, and 63), DXT1 (10), DXT5 (12), ETC_RGB4 (34), ETC2 (45–47), and ASTC (48–59) are available.
|
||||
|
||||
```ruby
|
||||
require 'mikunyan/decoders'
|
||||
@@ -104,23 +116,113 @@ require 'mikunyan/decoders'
|
||||
# get some Texture2D asset
|
||||
obj = asset.parse_object(path_ids[1])
|
||||
|
||||
# you can get Image object
|
||||
# you can get image data
|
||||
img = Mikunyan::ImageDecoder.decode_object(obj)
|
||||
|
||||
# save it!
|
||||
img.save('mikunyan.png')
|
||||
```
|
||||
|
||||
### Json Outputer
|
||||
Mikunyan cannot decode ASTC with HDR data. Use `Mikunyan::ImageDecoder.create_astc_file` instead.
|
||||
|
||||
`mikunyan-json` is the executable command for converting unity3d to json.
|
||||
### Json / YAML Outputter
|
||||
|
||||
`mikunyan-json` is an executable command for converting unity3d to JSON.
|
||||
|
||||
$ mikunyan-json bundle.unity3d > bundle.json
|
||||
|
||||
Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
|
||||
- `--pretty` (`-p`): prettify output json
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBundle but Asset
|
||||
- `--pretty` (`-p`): prettify output JSON
|
||||
- `--yaml` (`-y`): YAML mode
|
||||
|
||||
### Image Outputter
|
||||
|
||||
`mikunyan-image` is an executable command for unpacking images from unity3d.
|
||||
|
||||
$ mikunyan-image bundle.unity3d
|
||||
|
||||
The console log is JSON data of output textures as follows.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "bg_b",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": -744818715421265689
|
||||
},
|
||||
{
|
||||
"name": "bg_a",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": 5562124901460497987
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If the option `--sprite` specified, `mikunyan-image` will output sprites. The logged JSON also contains sprite information.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "bg_a",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": 5562124901460497987,
|
||||
"sprites": [
|
||||
{
|
||||
"name": "bg_a_0",
|
||||
"x": 1.0,
|
||||
"y": 303.0,
|
||||
"width": 1022.0,
|
||||
"height": 720.0,
|
||||
"path_id": -7546240288260780845
|
||||
},
|
||||
{
|
||||
"name": "bg_a_1",
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"width": 720.0,
|
||||
"height": 258.0,
|
||||
"path_id": -5293490190204738553
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bg_b",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": -744818715421265689,
|
||||
"sprites": [
|
||||
{
|
||||
"name": "bg_b_1",
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"width": 720.0,
|
||||
"height": 258.0,
|
||||
"path_id": 4884595733995530103
|
||||
},
|
||||
{
|
||||
"name": "bg_b_0",
|
||||
"x": 1.0,
|
||||
"y": 303.0,
|
||||
"width": 1022.0,
|
||||
"height": 720.0,
|
||||
"path_id": 7736251300187116441
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBundle but Asset
|
||||
- `--outputdir` (`-o`): specify an output directory (default is a basename of input file without an extension)
|
||||
- `--sprite` (`-s`): output sprites instead of textures
|
||||
- `--pretty` (`-p`): prettify output JSON
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -129,21 +231,11 @@ Available options:
|
||||
- [bin_utils](https://rubygems.org/gems/bin_utils)
|
||||
- [chunky_png](https://rubygems.org/gems/chunky_png)
|
||||
|
||||
Mikunyan use [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
||||
Mikunyan uses [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
||||
|
||||
## FAQ
|
||||
## Implementation in other languages
|
||||
|
||||
### Sometimes unpacking fails
|
||||
|
||||
I'm sorry...
|
||||
|
||||
### Can I unpack Mesh files?
|
||||
|
||||
It's hard work for me...
|
||||
|
||||
### What mikunyan comes from?
|
||||
|
||||
[Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
|
||||
- TypeScript: [shibunyan](https://github.com/AnemoneStar/shibunyan)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
require "bundler/gem_tasks"
|
||||
require "rake/extensiontask"
|
||||
|
||||
task :scream do
|
||||
puts "みくは自分を曲げないよ!"
|
||||
end
|
||||
|
||||
task :build => :compile
|
||||
|
||||
Rake::ExtensionTask.new('decoders/native') do |ext|
|
||||
ext.lib_dir = 'lib/mikunyan'
|
||||
end
|
||||
|
||||
task :default => [:clobber, :compile, :spec]
|
||||
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'mikunyan'
|
||||
require 'mikunyan/decoders'
|
||||
require 'fileutils'
|
||||
require 'json'
|
||||
|
||||
opts = {:as_asset => false, :outputdir => nil, :sprite => false, :pretty => false}
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
if ARGV[i].start_with?('-')
|
||||
case ARGV[i]
|
||||
when '--as-asset', '-a'
|
||||
opts[:as_asset] = true
|
||||
when '--outputdir', '-o'
|
||||
i += 1
|
||||
opts[:outputdir] = ARGV[i]
|
||||
when '--sprite', '-s'
|
||||
opts[:sprite] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
end
|
||||
else
|
||||
arg = ARGV[i] unless arg
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
unless arg
|
||||
warn("Input file is not specified")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
assets = []
|
||||
|
||||
if opts[:as_asset]
|
||||
assets = [Mikunyan::Asset.file(arg, File.basename(arg, '.*'))]
|
||||
else
|
||||
assets = Mikunyan::AssetBundle.file(arg).assets
|
||||
end
|
||||
|
||||
outdir = opts[:outputdir] || File.basename(arg, '.*')
|
||||
FileUtils.mkpath(outdir)
|
||||
|
||||
assets.each do |asset|
|
||||
if opts[:sprite]
|
||||
json = {}
|
||||
textures = {}
|
||||
|
||||
asset.objects.select{|o| asset.object_type(o) == 'Sprite'}.each do |o|
|
||||
obj = asset.parse_object(o)
|
||||
next unless obj
|
||||
name = obj.m_Name.value
|
||||
tex_id = obj.m_RD.texture.m_PathID.value
|
||||
|
||||
unless textures[tex_id]
|
||||
tex_obj = asset.parse_object(tex_id)
|
||||
if tex_obj
|
||||
textures[tex_id] = Mikunyan::ImageDecoder.decode_object(tex_obj) if tex_obj
|
||||
json[tex_id] = {:name => tex_obj.m_Name.value, :width => textures[tex_id].width, :height => textures[tex_id].height, :path_id => tex_id, :sprites => []}
|
||||
end
|
||||
end
|
||||
|
||||
if textures[tex_id]
|
||||
x = obj.m_Rect.x.value
|
||||
y = obj.m_Rect.y.value
|
||||
width = obj.m_Rect.width.value
|
||||
height = obj.m_Rect.height.value
|
||||
|
||||
json[tex_id][:sprites] << {:name => name, :x => x, :y => y, :width => width, :height => height, :path_id => o.path_id}
|
||||
textures[tex_id].crop(x.round, (textures[tex_id].height - height - y).round, width.round, height.round).save("#{outdir}/#{name}.png")
|
||||
end
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json.values) : JSON.generate(json.values)
|
||||
else
|
||||
json = []
|
||||
asset.objects.select{|o| asset.object_type(o) == 'Texture2D'}.each do |o|
|
||||
obj = asset.parse_object(o)
|
||||
next unless obj
|
||||
name = obj.m_Name.value
|
||||
image = Mikunyan::ImageDecoder.decode_object(obj)
|
||||
if image
|
||||
json << {:name => name, :width => image.width, :height => image.height, :path_id => o.path_id}
|
||||
image.save("#{outdir}/#{name}.png")
|
||||
end
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json) : JSON.generate(json)
|
||||
end
|
||||
end
|
||||
+19
-8
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'mikunyan'
|
||||
require 'json'
|
||||
require 'base64'
|
||||
|
||||
def obj64(obj)
|
||||
@@ -19,7 +18,7 @@ def obj64(obj)
|
||||
end
|
||||
end
|
||||
|
||||
opts = {:as_asset => false, :pretty => false}
|
||||
opts = {:as_asset => false, :pretty => false, :yaml => false}
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
@@ -29,9 +28,10 @@ while i < ARGV.count
|
||||
opts[:as_asset] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
when '--yaml', '-y'
|
||||
opts[:yaml] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
exit(1)
|
||||
end
|
||||
else
|
||||
arg = ARGV[i] unless arg
|
||||
@@ -39,6 +39,10 @@ while i < ARGV.count
|
||||
i += 1
|
||||
end
|
||||
|
||||
if opts[:pretty] && opts[:yaml]
|
||||
warn("Option --pretty is ignored if --yaml is specified.")
|
||||
end
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
@@ -51,7 +55,7 @@ if opts[:as_asset]
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj64(obj) if obj
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
else
|
||||
@@ -60,14 +64,21 @@ else
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj64(obj) if obj
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
end
|
||||
end
|
||||
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
if opts[:yaml]
|
||||
require 'yaml'
|
||||
puts YAML.dump(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
require 'json'
|
||||
assets = assets.map{|k, v| [k, obj64(v)]}.to_h
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,760 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ruby.h>
|
||||
#include "astc.h"
|
||||
|
||||
static const int BitReverseTable[] = {
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
|
||||
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
|
||||
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
|
||||
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
|
||||
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
|
||||
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
|
||||
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
|
||||
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
|
||||
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
|
||||
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
|
||||
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
|
||||
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
|
||||
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
|
||||
};
|
||||
|
||||
static int WeightPrecTableA[] = {0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0};
|
||||
static int WeightPrecTableB[] = {0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5};
|
||||
|
||||
static int CemTableA[] = {0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0};
|
||||
static int CemTableB[] = {8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1};
|
||||
|
||||
static inline uint_fast32_t color(uint_fast32_t r, uint_fast32_t g, uint_fast32_t b, uint_fast32_t a) {
|
||||
return r | g << 8 | b << 16 | a << 24;
|
||||
}
|
||||
|
||||
static inline uint_fast8_t bit_reverse_u8(const uint_fast8_t c, const int bits) {
|
||||
return BitReverseTable[c] >> (8 - bits);
|
||||
}
|
||||
|
||||
static inline uint_fast64_t bit_reverse_u64(const uint_fast64_t d, const int bits) {
|
||||
uint_fast64_t ret =
|
||||
(uint_fast64_t)BitReverseTable[d & 0xff] << 56 |
|
||||
(uint_fast64_t)BitReverseTable[d >> 8 & 0xff] << 48 |
|
||||
(uint_fast64_t)BitReverseTable[d >> 16 & 0xff] << 40 |
|
||||
(uint_fast64_t)BitReverseTable[d >> 24 & 0xff] << 32 |
|
||||
(uint_fast32_t)BitReverseTable[d >> 32 & 0xff] << 24 |
|
||||
(uint_fast32_t)BitReverseTable[d >> 40 & 0xff] << 16 |
|
||||
(uint_fast16_t)BitReverseTable[d >> 48 & 0xff] << 8 | BitReverseTable[d >> 56 & 0xff];
|
||||
return ret >> (64 - bits);
|
||||
}
|
||||
|
||||
static inline int getbits(const uint8_t *buf, const int bit, const int len) {
|
||||
return (*(int*)(buf + bit / 8) >> (bit % 8)) & ((1 << len) - 1);
|
||||
}
|
||||
|
||||
static inline uint_fast64_t getbits64(const uint8_t *buf, const int bit, const int len) {
|
||||
uint_fast64_t mask = len == 64 ? 0xffffffffffffffff : (1ull << len) - 1;
|
||||
if (len < 1)
|
||||
return 0;
|
||||
else if (bit >= 64)
|
||||
return (*(uint_fast64_t*)(buf + 8)) >> (bit - 64) & mask;
|
||||
else if (bit <= 0)
|
||||
return (*(uint_fast64_t*)buf) << -bit & mask;
|
||||
else if (bit + len <= 64)
|
||||
return (*(uint_fast64_t*)buf) >> bit & mask;
|
||||
else
|
||||
return ((*(uint_fast64_t*)buf) >> bit | *(uint_fast64_t*)(buf + 8) << (64 - bit)) & mask;
|
||||
}
|
||||
|
||||
static inline uint_fast8_t clamp(const int n) {
|
||||
return n < 0 ? 0 : n > 255 ? 255 : n;
|
||||
}
|
||||
|
||||
static inline void bit_transfer_signed(int *a, int *b) {
|
||||
*b = (*b >> 1) | (*a & 0x80);
|
||||
*a = (*a >> 1) & 0x3f;
|
||||
if (*a & 0x20)
|
||||
*a -= 0x40;
|
||||
}
|
||||
|
||||
static inline void set_endpoint(int endpoint[8], int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2) {
|
||||
endpoint[0] = r1;
|
||||
endpoint[1] = g1;
|
||||
endpoint[2] = b1;
|
||||
endpoint[3] = a1;
|
||||
endpoint[4] = r2;
|
||||
endpoint[5] = g2;
|
||||
endpoint[6] = b2;
|
||||
endpoint[7] = a2;
|
||||
}
|
||||
|
||||
static inline void set_endpoint_clamp(int endpoint[8], int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2) {
|
||||
endpoint[0] = clamp(r1);
|
||||
endpoint[1] = clamp(g1);
|
||||
endpoint[2] = clamp(b1);
|
||||
endpoint[3] = clamp(a1);
|
||||
endpoint[4] = clamp(r2);
|
||||
endpoint[5] = clamp(g2);
|
||||
endpoint[6] = clamp(b2);
|
||||
endpoint[7] = clamp(a2);
|
||||
}
|
||||
|
||||
static inline void set_endpoint_blue(int endpoint[8], int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2) {
|
||||
endpoint[0] = (r1 + b1) >> 1;
|
||||
endpoint[1] = (g1 + b1) >> 1;
|
||||
endpoint[2] = b1;
|
||||
endpoint[3] = a1;
|
||||
endpoint[4] = (r2 + b2) >> 1;
|
||||
endpoint[5] = (g2 + b2) >> 1;
|
||||
endpoint[6] = b2;
|
||||
endpoint[7] = a2;
|
||||
}
|
||||
|
||||
static inline void set_endpoint_blue_clamp(int endpoint[8], int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2) {
|
||||
endpoint[0] = clamp((r1 + b1) >> 1);
|
||||
endpoint[1] = clamp((g1 + b1) >> 1);
|
||||
endpoint[2] = clamp(b1);
|
||||
endpoint[3] = clamp(a1);
|
||||
endpoint[4] = clamp((r2 + b2) >> 1);
|
||||
endpoint[5] = clamp((g2 + b2) >> 1);
|
||||
endpoint[6] = clamp(b2);
|
||||
endpoint[7] = clamp(a2);
|
||||
}
|
||||
|
||||
static inline uint_fast8_t select_color(int v0, int v1, int weight) {
|
||||
return ((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * 255 + 32768) / 65536;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int bw;
|
||||
int bh;
|
||||
int width;
|
||||
int height;
|
||||
int part_num;
|
||||
int dual_plane;
|
||||
int plane_selector;
|
||||
int weight_range;
|
||||
int weight_num; // max: 120
|
||||
int cem[4];
|
||||
int cem_range;
|
||||
int endpoint_value_num; // max: 32
|
||||
int endpoints[4][8];
|
||||
int weights[144][2];
|
||||
int partition[144];
|
||||
} BlockData;
|
||||
|
||||
typedef struct {
|
||||
int bits;
|
||||
int nonbits;
|
||||
} IntSeqData;
|
||||
|
||||
void decode_intseq(const uint8_t *buf, int offset, const int a, const int b, const int count, const int reverse, IntSeqData *out) {
|
||||
static int mt[] = { 0, 2, 4, 5, 7 };
|
||||
static int mq[] = { 0, 3, 5 };
|
||||
static int TritsTable[5][256] = {
|
||||
{0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2},
|
||||
{0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1},
|
||||
{0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}
|
||||
};
|
||||
static int QuintsTable[3][128] = {
|
||||
{0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1},
|
||||
{0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3},
|
||||
{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4}
|
||||
};
|
||||
|
||||
if (count <= 0)
|
||||
return;
|
||||
|
||||
int n = 0;
|
||||
|
||||
if (a == 3) {
|
||||
int mask = (1 << b) - 1;
|
||||
int block_count = (count + 4) / 5;
|
||||
int last_block_count = (count + 4) % 5 + 1;
|
||||
int block_size = 8 + 5 * b;
|
||||
int last_block_size = (block_size * last_block_count + 4) / 5;
|
||||
|
||||
if (reverse) {
|
||||
for (int i = 0, p = offset; i < block_count; i++, p -= block_size) {
|
||||
int now_size = (i < block_count - 1) ? block_size : last_block_size;
|
||||
uint_fast64_t d = bit_reverse_u64(getbits64(buf, p - now_size, now_size), now_size);
|
||||
int x = (d >> b & 3) | (d >> b * 2 & 0xc) | (d >> b * 3 & 0x10) | (d >> b * 4 & 0x60) | (d >> b * 5 & 0x80);
|
||||
for (int j = 0; j < 5 && n < count; j++, n++)
|
||||
out[n] = (IntSeqData){ d >> (mt[j] + b * j) & mask, TritsTable[j][x] };
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, p = offset; i < block_count; i++, p += block_size) {
|
||||
uint_fast64_t d = getbits64(buf, p, (i < block_count - 1) ? block_size : last_block_size);
|
||||
int x = (d >> b & 3) | (d >> b * 2 & 0xc) | (d >> b * 3 & 0x10) | (d >> b * 4 & 0x60) | (d >> b * 5 & 0x80);
|
||||
for (int j = 0; j < 5 && n < count; j++, n++)
|
||||
out[n] = (IntSeqData){ d >> (mt[j] + b * j) & mask, TritsTable[j][x] };
|
||||
}
|
||||
}
|
||||
} else if (a == 5) {
|
||||
int mask = (1 << b) - 1;
|
||||
int block_count = (count + 2) / 3;
|
||||
int last_block_count = (count + 2) % 3 + 1;
|
||||
int block_size = 7 + 3 * b;
|
||||
int last_block_size = (block_size * last_block_count + 2) / 3;
|
||||
|
||||
if (reverse) {
|
||||
for (int i = 0, p = offset; i < block_count; i++, p -= block_size) {
|
||||
int now_size = (i < block_count - 1) ? block_size : last_block_size;
|
||||
uint_fast64_t d = bit_reverse_u64(getbits64(buf, p - now_size, now_size), now_size);
|
||||
int x = (d >> b & 7) | (d >> b * 2 & 0x18) | (d >> b * 3 & 0x60);
|
||||
for (int j = 0; j < 3 && n < count; j++, n++)
|
||||
out[n] = (IntSeqData){ d >> (mq[j] + b * j) & mask, QuintsTable[j][x] };
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, p = offset; i < block_count; i++, p += block_size) {
|
||||
uint_fast64_t d = getbits64(buf, p, (i < block_count - 1) ? block_size : last_block_size);
|
||||
int x = (d >> b & 7) | (d >> b * 2 & 0x18) | (d >> b * 3 & 0x60);
|
||||
for (int j = 0; j < 3 && n < count; j++, n++)
|
||||
out[n] = (IntSeqData){ d >> (mq[j] + b * j) & mask, QuintsTable[j][x] };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (reverse)
|
||||
for (int p = offset - b; n < count; n++, p -= b)
|
||||
out[n] = (IntSeqData){ bit_reverse_u8(getbits(buf, p, b), b), 0 };
|
||||
else
|
||||
for (int p = offset; n < count; n++, p += b)
|
||||
out[n] = (IntSeqData){ getbits(buf, p, b), 0 };
|
||||
}
|
||||
}
|
||||
|
||||
void decode_block_params(const uint8_t *buf, BlockData *block_data) {
|
||||
block_data->dual_plane = (buf[1] & 4) >> 2;
|
||||
block_data->weight_range = (buf[0] >> 4 & 1) | (buf[1] << 2 & 8);
|
||||
|
||||
if (buf[0] & 3) {
|
||||
block_data->weight_range |= buf[0] << 1 & 6;
|
||||
switch (buf[0] & 0xc) {
|
||||
case 0:
|
||||
block_data->width = (*(int*)buf >> 7 & 3) + 4;
|
||||
block_data->height = (buf[0] >> 5 & 3) + 2;
|
||||
break;
|
||||
case 4:
|
||||
block_data->width = (*(int*)buf >> 7 & 3) + 8;
|
||||
block_data->height = (buf[0] >> 5 & 3) + 2;
|
||||
break;
|
||||
case 8:
|
||||
block_data->width = (buf[0] >> 5 & 3) + 2;
|
||||
block_data->height = (*(int*)buf >> 7 & 3) + 8;
|
||||
break;
|
||||
case 12:
|
||||
if (buf[1] & 1) {
|
||||
block_data->width = (buf[0] >> 7 & 1) + 2;
|
||||
block_data->height = (buf[0] >> 5 & 3) + 2;
|
||||
} else {
|
||||
block_data->width = (buf[0] >> 5 & 3) + 2;
|
||||
block_data->height = (buf[0] >> 7 & 1) + 6;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
block_data->weight_range |= buf[0] >> 1 & 6;
|
||||
switch ((*(int*)buf) & 0x180) {
|
||||
case 0:
|
||||
block_data->width = 12;
|
||||
block_data->height = (buf[0] >> 5 & 3) + 2;
|
||||
break;
|
||||
case 0x80:
|
||||
block_data->width = (buf[0] >> 5 & 3) + 2;
|
||||
block_data->height = 12;
|
||||
break;
|
||||
case 0x100:
|
||||
block_data->width = (buf[0] >> 5 & 3) + 6;
|
||||
block_data->height = (buf[1] >> 1 & 3) + 6;
|
||||
block_data->dual_plane = 0;
|
||||
block_data->weight_range &= 7;
|
||||
break;
|
||||
case 0x180:
|
||||
block_data->width = (buf[0] & 0x20) ? 10 : 6;
|
||||
block_data->height = (buf[0] & 0x20) ? 6 : 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
block_data->part_num = (buf[1] >> 3 & 3) + 1;
|
||||
|
||||
block_data->weight_num = block_data->width * block_data->height;
|
||||
if (block_data->dual_plane)
|
||||
block_data->weight_num *= 2;
|
||||
|
||||
int weight_bits, config_bits, cem_base = 0;
|
||||
|
||||
switch (WeightPrecTableA[block_data->weight_range]) {
|
||||
case 3:
|
||||
weight_bits = block_data->weight_num * WeightPrecTableB[block_data->weight_range] + (block_data->weight_num * 8 + 4) / 5;
|
||||
break;
|
||||
case 5:
|
||||
weight_bits = block_data->weight_num * WeightPrecTableB[block_data->weight_range] + (block_data->weight_num * 7 + 2) / 3;
|
||||
break;
|
||||
default:
|
||||
weight_bits = block_data->weight_num * WeightPrecTableB[block_data->weight_range];
|
||||
}
|
||||
|
||||
if (block_data->part_num == 1) {
|
||||
block_data->cem[0] = *(int*)(buf + 1) >> 5 & 0xf;
|
||||
config_bits = 17;
|
||||
} else {
|
||||
cem_base = *(int*)(buf + 2) >> 7 & 3;
|
||||
if (cem_base == 0) {
|
||||
int cem = buf[3] >> 1 & 0xf;
|
||||
for (int i = 0; i < block_data->part_num; i++)
|
||||
block_data->cem[i] = cem;
|
||||
config_bits = 29;
|
||||
} else {
|
||||
for (int i = 0; i < block_data->part_num; i++)
|
||||
block_data->cem[i] = ((buf[3] >> (i + 1) & 1) + cem_base - 1) << 2;
|
||||
switch (block_data->part_num) {
|
||||
case 2:
|
||||
block_data->cem[0] |= buf[3] >> 3 & 3;
|
||||
block_data->cem[1] |= getbits(buf, 126 - weight_bits, 2);
|
||||
break;
|
||||
case 3:
|
||||
block_data->cem[0] |= buf[3] >> 4 & 1;
|
||||
block_data->cem[0] |= getbits(buf, 122 - weight_bits, 2) & 2;
|
||||
block_data->cem[1] |= getbits(buf, 124 - weight_bits, 2);
|
||||
block_data->cem[2] |= getbits(buf, 126 - weight_bits, 2);
|
||||
break;
|
||||
case 4:
|
||||
for (int i = 0; i < 4; i++)
|
||||
block_data->cem[i] |= getbits(buf, 120 + i * 2 - weight_bits, 2);
|
||||
break;
|
||||
}
|
||||
config_bits = 25 + block_data->part_num * 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (block_data->dual_plane) {
|
||||
config_bits += 2;
|
||||
block_data->plane_selector = getbits(buf, cem_base ? 130 - weight_bits - block_data->part_num * 3 : 126 - weight_bits, 2);
|
||||
}
|
||||
|
||||
int remain_bits = 128 - config_bits - weight_bits;
|
||||
|
||||
block_data->endpoint_value_num = 0;
|
||||
for (int i = 0; i < block_data->part_num; i++)
|
||||
block_data->endpoint_value_num += (block_data->cem[i] >> 1 & 6) + 2;
|
||||
|
||||
for (int i = 0, endpoint_bits; i < (int)(sizeof(CemTableA) / sizeof(int)); i++) {
|
||||
switch (CemTableA[i]) {
|
||||
case 3:
|
||||
endpoint_bits = block_data->endpoint_value_num * CemTableB[i] + (block_data->endpoint_value_num * 8 + 4) / 5;
|
||||
break;
|
||||
case 5:
|
||||
endpoint_bits = block_data->endpoint_value_num * CemTableB[i] + (block_data->endpoint_value_num * 7 + 2) / 3;
|
||||
break;
|
||||
default:
|
||||
endpoint_bits = block_data->endpoint_value_num * CemTableB[i];
|
||||
}
|
||||
|
||||
if (endpoint_bits <= remain_bits) {
|
||||
block_data->cem_range = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decode_endpoints(const uint8_t *buf, BlockData *data) {
|
||||
static int TritsTable[] = { 0, 204, 93, 44, 22, 11, 5 };
|
||||
static int QuintsTable[] = { 0, 113, 54, 26, 13, 6 };
|
||||
IntSeqData seq[32];
|
||||
int ev[32];
|
||||
decode_intseq(buf, data->part_num == 1 ? 17 : 29, CemTableA[data->cem_range], CemTableB[data->cem_range], data->endpoint_value_num, 0, seq);
|
||||
|
||||
switch (CemTableA[data->cem_range]) {
|
||||
case 3:
|
||||
for (int i = 0, b, c = TritsTable[CemTableB[data->cem_range]]; i < data->endpoint_value_num; i++) {
|
||||
int a = (seq[i].bits & 1) * 0x1ff;
|
||||
int x = seq[i].bits >> 1;
|
||||
switch (CemTableB[data->cem_range]) {
|
||||
case 1:
|
||||
b = 0;
|
||||
break;
|
||||
case 2:
|
||||
b = 0b100010110 * x;
|
||||
break;
|
||||
case 3:
|
||||
b = x << 7 | x << 2 | x;
|
||||
break;
|
||||
case 4:
|
||||
b = x << 6 | x;
|
||||
break;
|
||||
case 5:
|
||||
b = x << 5 | x >> 2;
|
||||
break;
|
||||
case 6:
|
||||
b = x << 4 | x >> 4;
|
||||
break;
|
||||
}
|
||||
ev[i] = (a & 0x80) | ((seq[i].nonbits * c + b) ^ a) >> 2;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
for (int i = 0, b, c = QuintsTable[CemTableB[data->cem_range]]; i < data->endpoint_value_num; i++) {
|
||||
int a = (seq[i].bits & 1) * 0x1ff;
|
||||
int x = seq[i].bits >> 1;
|
||||
switch (CemTableB[data->cem_range]) {
|
||||
case 1:
|
||||
b = 0;
|
||||
break;
|
||||
case 2:
|
||||
b = 0b100001100 * x;
|
||||
break;
|
||||
case 3:
|
||||
b = x << 7 | x << 1 | x >> 1;
|
||||
break;
|
||||
case 4:
|
||||
b = x << 6 | x >> 1;
|
||||
break;
|
||||
case 5:
|
||||
b = x << 5 | x >> 3;
|
||||
break;
|
||||
}
|
||||
ev[i] = (a & 0x80) | ((seq[i].nonbits * c + b) ^ a) >> 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch (CemTableB[data->cem_range]) {
|
||||
case 1:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits * 0xff;
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits * 0x55;
|
||||
break;
|
||||
case 3:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits << 5 | seq[i].bits << 2 | seq[i].bits >> 1;
|
||||
break;
|
||||
case 4:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits << 4 | seq[i].bits;
|
||||
break;
|
||||
case 5:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits << 3 | seq[i].bits >> 2;
|
||||
break;
|
||||
case 6:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits << 2 | seq[i].bits >> 4;
|
||||
break;
|
||||
case 7:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits << 1 | seq[i].bits >> 6;
|
||||
break;
|
||||
case 8:
|
||||
for (int i = 0; i < data->endpoint_value_num; i++)
|
||||
ev[i] = seq[i].bits;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int *v = ev;
|
||||
for (int cem = 0; cem < data->part_num; v += (data->cem[cem] / 4 + 1) * 2, cem++) {
|
||||
switch (data->cem[cem]) {
|
||||
case 0:
|
||||
set_endpoint(data->endpoints[cem], v[0], v[0], v[0], 255, v[1], v[1], v[1], 255);
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
int l0 = (v[0] >> 2) | (v[1] & 0xc0);
|
||||
int l1 = clamp(l0 + (v[1] & 0x3f));
|
||||
set_endpoint(data->endpoints[cem], l0, l0, l0, 255, l1, l1, l1, 255);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
set_endpoint(data->endpoints[cem], v[0], v[0], v[0], v[2], v[1], v[1], v[1], v[3]);
|
||||
break;
|
||||
case 5:
|
||||
bit_transfer_signed(&v[1], &v[0]);
|
||||
bit_transfer_signed(&v[3], &v[2]);
|
||||
v[1] += v[0];
|
||||
set_endpoint_clamp(data->endpoints[cem], v[0], v[0], v[0], v[2], v[1], v[1], v[1], v[2] + v[3]);
|
||||
break;
|
||||
case 6:
|
||||
set_endpoint(data->endpoints[cem], v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, 255, v[0], v[1], v[2], 255);
|
||||
break;
|
||||
case 8:
|
||||
if (v[0] + v[2] + v[4] <= v[1] + v[3] + v[5])
|
||||
set_endpoint(data->endpoints[cem], v[0], v[2], v[4], 255, v[1], v[3], v[5], 255);
|
||||
else
|
||||
set_endpoint_blue(data->endpoints[cem], v[1], v[3], v[5], 255, v[0], v[2], v[4], 255);
|
||||
break;
|
||||
case 9:
|
||||
bit_transfer_signed(&v[1], &v[0]);
|
||||
bit_transfer_signed(&v[3], &v[2]);
|
||||
bit_transfer_signed(&v[5], &v[4]);
|
||||
if (v[1] + v[3] + v[5] >= 0)
|
||||
set_endpoint_clamp(data->endpoints[cem], v[0], v[2], v[4], 255, v[0] + v[1], v[2] + v[3], v[4] + v[5], 255);
|
||||
else
|
||||
set_endpoint_blue_clamp(data->endpoints[cem], v[0] + v[1], v[2] + v[3], v[4] + v[5], 255, v[0], v[2], v[4], 255);
|
||||
break;
|
||||
case 10:
|
||||
set_endpoint(data->endpoints[cem], v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, v[4], v[0], v[1], v[2], v[5]);
|
||||
break;
|
||||
case 12:
|
||||
if (v[0] + v[2] + v[4] <= v[1] + v[3] + v[5])
|
||||
set_endpoint(data->endpoints[cem], v[0], v[2], v[4], v[6], v[1], v[3], v[5], v[7]);
|
||||
else
|
||||
set_endpoint_blue(data->endpoints[cem], v[1], v[3], v[5], v[7], v[0], v[2], v[4], v[6]);
|
||||
break;
|
||||
case 13:
|
||||
bit_transfer_signed(&v[1], &v[0]);
|
||||
bit_transfer_signed(&v[3], &v[2]);
|
||||
bit_transfer_signed(&v[5], &v[4]);
|
||||
bit_transfer_signed(&v[7], &v[6]);
|
||||
if (v[1] + v[3] + v[5] >= 0)
|
||||
set_endpoint_clamp(data->endpoints[cem], v[0], v[2], v[4], v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7]);
|
||||
else
|
||||
set_endpoint_blue_clamp(data->endpoints[cem], v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7], v[0], v[2], v[4], v[6]);
|
||||
break;
|
||||
default:
|
||||
rb_raise(rb_eStandardError, "Unsupported ASTC format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decode_weights(const uint8_t *buf, BlockData *data) {
|
||||
IntSeqData seq[128];
|
||||
int wv[128] = {};
|
||||
decode_intseq(buf, 128, WeightPrecTableA[data->weight_range], WeightPrecTableB[data->weight_range], data->weight_num, 1, seq);
|
||||
|
||||
if (WeightPrecTableA[data->weight_range] == 0) {
|
||||
switch (WeightPrecTableB[data->weight_range]) {
|
||||
case 1:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].bits ? 63 : 0;
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].bits << 4 | seq[i].bits << 2 | seq[i].bits;
|
||||
break;
|
||||
case 3:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].bits << 3 | seq[i].bits;
|
||||
break;
|
||||
case 4:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].bits << 2 | seq[i].bits >> 2;
|
||||
break;
|
||||
case 5:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].bits << 1 | seq[i].bits >> 4;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
if (wv[i] > 32)
|
||||
++wv[i];
|
||||
} else if (WeightPrecTableB[data->weight_range] == 0) {
|
||||
int s = WeightPrecTableA[data->weight_range] == 3 ? 32 : 16;
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].nonbits * s;
|
||||
} else {
|
||||
if (WeightPrecTableA[data->weight_range] == 3) {
|
||||
switch (WeightPrecTableB[data->weight_range]) {
|
||||
case 1:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].nonbits * 50;
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < data->weight_num; i++) {
|
||||
wv[i] = seq[i].nonbits * 23;
|
||||
if (seq[i].bits & 2)
|
||||
wv[i] += 0b1000101;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].nonbits * 11 + ((seq[i].bits << 4 | seq[i].bits >> 1) & 0b1100011);
|
||||
break;
|
||||
}
|
||||
} else if (WeightPrecTableA[data->weight_range] == 5) {
|
||||
switch (WeightPrecTableB[data->weight_range]) {
|
||||
case 1:
|
||||
for (int i = 0; i < data->weight_num; i++)
|
||||
wv[i] = seq[i].nonbits * 28;
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < data->weight_num; i++) {
|
||||
wv[i] = seq[i].nonbits * 13;
|
||||
if (seq[i].bits & 2)
|
||||
wv[i] += 0b1000010;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < data->weight_num; i++) {
|
||||
int a = (seq[i].bits & 1) * 0x7f;
|
||||
wv[i] = (a & 0x20) | ((wv[i] ^ a) >> 2);
|
||||
if (wv[i] > 32)
|
||||
++wv[i];
|
||||
}
|
||||
}
|
||||
|
||||
int ds = (1024 + data->bw / 2) / (data->bw - 1);
|
||||
int dt = (1024 + data->bh / 2) / (data->bh - 1);
|
||||
int pn = data->dual_plane ? 2 : 1;
|
||||
|
||||
for (int t = 0, i = 0; t < data->bh; t++) {
|
||||
for (int s = 0; s < data->bw; s++, i++) {
|
||||
int gs = (ds * s * (data->width - 1) + 32) >> 6;
|
||||
int gt = (dt * t * (data->height - 1) + 32) >> 6;
|
||||
int fs = gs & 0xf;
|
||||
int ft = gt & 0xf;
|
||||
int v = (gs >> 4) + (gt >> 4) * data->width;
|
||||
int w11 = (fs * ft + 8) >> 4;
|
||||
int w10 = ft - w11;
|
||||
int w01 = fs - w11;
|
||||
int w00 = 16 - fs - ft + w11;
|
||||
|
||||
for (int p = 0; p < pn; p++) {
|
||||
int p00 = wv[v * pn + p];
|
||||
int p01 = wv[(v + 1) * pn + p];
|
||||
int p10 = wv[(v + data->width) * pn + p];
|
||||
int p11 = wv[(v + data->width + 1) * pn + p];
|
||||
data->weights[i][p] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void select_partition(const uint8_t *buf, BlockData *data) {
|
||||
int small_block = data->bw * data->bh < 31;
|
||||
int seed = (*(int*)buf >> 13 & 0x3ff) | (data->part_num - 1) << 10;
|
||||
|
||||
uint32_t rnum = seed;
|
||||
rnum ^= rnum >> 15;
|
||||
rnum -= rnum << 17;
|
||||
rnum += rnum << 7;
|
||||
rnum += rnum << 4;
|
||||
rnum ^= rnum >> 5;
|
||||
rnum += rnum << 16;
|
||||
rnum ^= rnum >> 7;
|
||||
rnum ^= rnum >> 3;
|
||||
rnum ^= rnum << 6;
|
||||
rnum ^= rnum >> 17;
|
||||
|
||||
int seeds[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
seeds[i] = (rnum >> (i * 4)) & 0xF;
|
||||
seeds[i] *= seeds[i];
|
||||
}
|
||||
|
||||
int sh[2] = { seed & 2 ? 4 : 5, data->part_num == 3 ? 6 : 5 };
|
||||
|
||||
if (seed & 1)
|
||||
for (int i = 0; i < 8; i++)
|
||||
seeds[i] >>= sh[i % 2];
|
||||
else
|
||||
for (int i = 0; i < 8; i++)
|
||||
seeds[i] >>= sh[1 - i % 2];
|
||||
|
||||
if (small_block) {
|
||||
for (int t = 0, i = 0; t < data->bh; t++) {
|
||||
for (int s = 0; s < data->bw; s++, i++) {
|
||||
int x = s << 1;
|
||||
int y = t << 1;
|
||||
int a = (seeds[0] * x + seeds[1] * y + (rnum >> 14)) & 0x3f;
|
||||
int b = (seeds[2] * x + seeds[3] * y + (rnum >> 10)) & 0x3f;
|
||||
int c = data->part_num < 3 ? 0 : (seeds[4] * x + seeds[5] * y + (rnum >> 6)) & 0x3f;
|
||||
int d = data->part_num < 4 ? 0 : (seeds[6] * x + seeds[7] * y + (rnum >> 2)) & 0x3f;
|
||||
data->partition[i] = (a >= b && a >= c && a >= d) ? 0 : (b >= c && b >= d) ? 1 : (c >= d) ? 2 : 3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int y = 0, i = 0; y < data->bh; y++) {
|
||||
for (int x = 0; x < data->bw; x++, i++) {
|
||||
int a = (seeds[0] * x + seeds[1] * y + (rnum >> 14)) & 0x3f;
|
||||
int b = (seeds[2] * x + seeds[3] * y + (rnum >> 10)) & 0x3f;
|
||||
int c = data->part_num < 3 ? 0 : (seeds[4] * x + seeds[5] * y + (rnum >> 6)) & 0x3f;
|
||||
int d = data->part_num < 4 ? 0 : (seeds[6] * x + seeds[7] * y + (rnum >> 2)) & 0x3f;
|
||||
data->partition[i] = (a >= b && a >= c && a >= d) ? 0 : (b >= c && b >= d) ? 1 : (c >= d) ? 2 : 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void applicate_color(const BlockData *data, uint32_t *outbuf) {
|
||||
if (data->dual_plane) {
|
||||
int ps[] = { 0, 0, 0, 0 };
|
||||
ps[data->plane_selector] = 1;
|
||||
if (data->part_num > 1) {
|
||||
for (int i = 0; i < data->bw * data->bh; i++) {
|
||||
int p = data->partition[i];
|
||||
uint_fast8_t r = select_color(data->endpoints[p][0], data->endpoints[p][4], data->weights[i][ps[0]]);
|
||||
uint_fast8_t g = select_color(data->endpoints[p][1], data->endpoints[p][5], data->weights[i][ps[1]]);
|
||||
uint_fast8_t b = select_color(data->endpoints[p][2], data->endpoints[p][6], data->weights[i][ps[2]]);
|
||||
uint_fast8_t a = select_color(data->endpoints[p][3], data->endpoints[p][7], data->weights[i][ps[3]]);
|
||||
outbuf[i] = color(r, g, b, a);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < data->bw * data->bh; i++) {
|
||||
uint_fast8_t r = select_color(data->endpoints[0][0], data->endpoints[0][4], data->weights[i][ps[0]]);
|
||||
uint_fast8_t g = select_color(data->endpoints[0][1], data->endpoints[0][5], data->weights[i][ps[1]]);
|
||||
uint_fast8_t b = select_color(data->endpoints[0][2], data->endpoints[0][6], data->weights[i][ps[2]]);
|
||||
uint_fast8_t a = select_color(data->endpoints[0][3], data->endpoints[0][7], data->weights[i][ps[3]]);
|
||||
outbuf[i] = color(r, g, b, a);
|
||||
}
|
||||
}
|
||||
} else if (data->part_num > 1) {
|
||||
for (int i = 0; i < data->bw * data->bh; i++) {
|
||||
int p = data->partition[i];
|
||||
uint_fast8_t r = select_color(data->endpoints[p][0], data->endpoints[p][4], data->weights[i][0]);
|
||||
uint_fast8_t g = select_color(data->endpoints[p][1], data->endpoints[p][5], data->weights[i][0]);
|
||||
uint_fast8_t b = select_color(data->endpoints[p][2], data->endpoints[p][6], data->weights[i][0]);
|
||||
uint_fast8_t a = select_color(data->endpoints[p][3], data->endpoints[p][7], data->weights[i][0]);
|
||||
outbuf[i] = color(r, g, b, a);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < data->bw * data->bh; i++) {
|
||||
uint_fast8_t r = select_color(data->endpoints[0][0], data->endpoints[0][4], data->weights[i][0]);
|
||||
uint_fast8_t g = select_color(data->endpoints[0][1], data->endpoints[0][5], data->weights[i][0]);
|
||||
uint_fast8_t b = select_color(data->endpoints[0][2], data->endpoints[0][6], data->weights[i][0]);
|
||||
uint_fast8_t a = select_color(data->endpoints[0][3], data->endpoints[0][7], data->weights[i][0]);
|
||||
outbuf[i] = color(r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decode_block(const uint8_t *buf, const int bw, const int bh, uint32_t *outbuf) {
|
||||
if (buf[0] == 0xfc && (buf[1] & 1) == 1) {
|
||||
uint_fast32_t c = color(buf[9], buf[11], buf[13], buf[15]);
|
||||
for (int i = 0; i < bw * bh; i++)
|
||||
outbuf[i] = c;
|
||||
} else {
|
||||
BlockData block_data;
|
||||
block_data.bw = bw;
|
||||
block_data.bh = bh;
|
||||
decode_block_params(buf, &block_data);
|
||||
decode_endpoints(buf, &block_data);
|
||||
decode_weights(buf, &block_data);
|
||||
if (block_data.part_num > 1)
|
||||
select_partition(buf, &block_data);
|
||||
applicate_color(&block_data, outbuf);
|
||||
}
|
||||
}
|
||||
|
||||
void decode_astc(const uint8_t *data, const int w, const int h, const int bw, const int bh, uint32_t *image) {
|
||||
int bcw = (w + bw - 1) / bw;
|
||||
int bch = (h + bh - 1) / bh;
|
||||
int clen_last = (w + bw - 1) % bw + 1;
|
||||
uint32_t *buf = (uint32_t*)calloc(bw * bh, sizeof(uint32_t));
|
||||
const uint8_t *ptr = data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, ptr += 16) {
|
||||
decode_block(ptr, bw, bh, buf);
|
||||
int clen = (s < bcw - 1 ? bw : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * bh - 1; i < bh && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * bw, buf + i * bw, clen);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef ASTC_H
|
||||
#define ASTC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void decode_astc(const uint8_t*, const int, const int, const int, const int, uint32_t*);
|
||||
|
||||
#endif /* end of include guard: ASTC_H */
|
||||
@@ -0,0 +1,104 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "dxtc.h"
|
||||
|
||||
static inline uint_fast32_t color(uint_fast32_t r, uint_fast32_t g, uint_fast32_t b, uint_fast32_t a) {
|
||||
return r | g << 8 | b << 16 | a << 24;
|
||||
}
|
||||
|
||||
static inline void rgb565(const uint_fast16_t c, int *r, int *g, int *b) {
|
||||
*r = (c & 0xf800) >> 8;
|
||||
*g = (c & 0x07e0) >> 3;
|
||||
*b = (c & 0x001f) << 3;
|
||||
*r |= *r >> 5;
|
||||
*g |= *g >> 6;
|
||||
*b |= *b >> 5;
|
||||
}
|
||||
|
||||
static inline void decode_dxt1_block(const uint64_t *data, uint32_t *outbuf) {
|
||||
int r0, g0, b0, r1, g1, b1;
|
||||
int q0 = ((uint16_t*)data)[0];
|
||||
int q1 = ((uint16_t*)data)[1];
|
||||
rgb565(q0, &r0, &g0, &b0);
|
||||
rgb565(q1, &r1, &g1, &b1);
|
||||
uint_fast32_t c[4] = { color(r0, g0, b0, 255), color(r1, g1, b1, 255) };
|
||||
if (q0 > q1) {
|
||||
c[2] = color((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 255);
|
||||
c[3] = color((r0 + r1 * 2) / 3, (g0 + g1 * 2) / 3, (b0 + b1 * 2) / 3, 255);
|
||||
} else {
|
||||
c[2] = color((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 255);
|
||||
}
|
||||
uint_fast32_t d = *data >> 32;
|
||||
for (int i = 0; i < 16; i++, d >>= 2)
|
||||
outbuf[i] = c[d & 3];
|
||||
}
|
||||
|
||||
void decode_dxt1(const uint64_t *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint64_t *d = data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d++) {
|
||||
decode_dxt1_block(d, buf);
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void decode_dxt5_block(const uint64_t *data, uint32_t *outbuf) {
|
||||
uint_fast32_t a[8] = { ((uint8_t*)data)[0], ((uint8_t*)data)[1] };
|
||||
if (a[0] > a[1]) {
|
||||
a[2] = (a[0] * 6 + a[1] ) / 7;
|
||||
a[3] = (a[0] * 5 + a[1] * 2) / 7;
|
||||
a[4] = (a[0] * 4 + a[1] * 3) / 7;
|
||||
a[5] = (a[0] * 3 + a[1] * 4) / 7;
|
||||
a[6] = (a[0] * 2 + a[1] * 5) / 7;
|
||||
a[7] = (a[0] + a[1] * 6) / 7;
|
||||
} else {
|
||||
a[2] = (a[0] * 4 + a[1] ) / 5;
|
||||
a[3] = (a[0] * 3 + a[1] * 2) / 5;
|
||||
a[4] = (a[0] * 2 + a[1] * 3) / 5;
|
||||
a[5] = (a[0] + a[1] * 4) / 5;
|
||||
a[7] = 255;
|
||||
}
|
||||
for (int i = 0; i < 8; i++)
|
||||
a[i] <<= 24;
|
||||
|
||||
int r0, g0, b0, r1, g1, b1;
|
||||
int q0 = ((uint16_t*)(data + 1))[0];
|
||||
int q1 = ((uint16_t*)(data + 1))[1];
|
||||
rgb565(q0, &r0, &g0, &b0);
|
||||
rgb565(q1, &r1, &g1, &b1);
|
||||
uint_fast32_t c[4] = { color(r0, g0, b0, 0), color(r1, g1, b1, 0) };
|
||||
if (q0 > q1) {
|
||||
c[2] = color((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 0);
|
||||
c[3] = color((r0 + r1 * 2) / 3, (g0 + g1 * 2) / 3, (b0 + b1 * 2) / 3, 0);
|
||||
} else {
|
||||
c[2] = color((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 0);
|
||||
}
|
||||
|
||||
uint_fast64_t da = *data >> 16;
|
||||
uint_fast32_t dc = *(data + 1) >> 32;
|
||||
for (int i = 0; i < 16; i++, da >>= 3, dc >>= 2)
|
||||
outbuf[i] = a[da & 7] | c[dc & 3];
|
||||
}
|
||||
|
||||
void decode_dxt5(const uint64_t *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint64_t *d = data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d += 2) {
|
||||
decode_dxt5_block(d, buf);
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef DXTC_H
|
||||
#define DXTC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void decode_dxt1(const uint64_t*, const int, const int, uint32_t*);
|
||||
void decode_dxt5(const uint64_t*, const int, const int, uint32_t*);
|
||||
|
||||
#endif /* end of include guard: DXTC_H */
|
||||
@@ -0,0 +1,271 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "etc.h"
|
||||
|
||||
uint_fast8_t WriteOrderTable[16] = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 };
|
||||
uint_fast8_t WriteOrderTableRev[16] = { 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0 };
|
||||
uint_fast8_t Etc1ModifierTable[8][2] = {{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}};
|
||||
uint_fast8_t Etc1SubblockTable[2][16] = {{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}};
|
||||
uint_fast8_t Etc2DistanceTable[8] = {3, 6, 11, 16, 23, 32, 41, 64};
|
||||
int_fast8_t Etc2AlphaModTable[16][8] = {
|
||||
{-3, -6, -9, -15, 2, 5, 8, 14},
|
||||
{-3, -7, -10, -13, 2, 6, 9, 12},
|
||||
{-2, -5, -8, -13, 1, 4, 7, 12},
|
||||
{-2, -4, -6, -13, 1, 3, 5, 12},
|
||||
{-3, -6, -8, -12, 2, 5, 7, 11},
|
||||
{-3, -7, -9, -11, 2, 6, 8, 10},
|
||||
{-4, -7, -8, -11, 3, 6, 7, 10},
|
||||
{-3, -5, -8, -11, 2, 4, 7, 10},
|
||||
{-2, -6, -8, -10, 1, 5, 7, 9},
|
||||
{-2, -5, -8, -10, 1, 4, 7, 9},
|
||||
{-2, -4, -8, -10, 1, 3, 7, 9},
|
||||
{-2, -5, -7, -10, 1, 4, 6, 9},
|
||||
{-3, -4, -7, -10, 2, 3, 6, 9},
|
||||
{-1, -2, -3, -10, 0, 1, 2, 9},
|
||||
{-4, -6, -8, -9, 3, 5, 7, 8},
|
||||
{-3, -5, -7, -9, 2, 4, 6, 8}
|
||||
};
|
||||
|
||||
static inline uint_fast32_t color(uint_fast32_t r, uint_fast32_t g, uint_fast32_t b, uint_fast32_t a) {
|
||||
return r | g << 8 | b << 16 | a << 24;
|
||||
}
|
||||
|
||||
static inline uint_fast8_t clamp(const int n) {
|
||||
return n < 0 ? 0 : n > 255 ? 255 : n;
|
||||
}
|
||||
|
||||
static inline uint32_t applicate_color(uint_fast8_t c[3], int_fast16_t m) {
|
||||
return color(clamp(c[0] + m), clamp(c[1] + m), clamp(c[2] + m), 255);
|
||||
}
|
||||
|
||||
static inline uint32_t applicate_color_raw(uint_fast8_t c[3]) {
|
||||
return color(c[0], c[1], c[2], 255);
|
||||
}
|
||||
|
||||
static inline void decode_etc1_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
|
||||
uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
uint_fast8_t c[2][3];
|
||||
if (data[3] & 2) {
|
||||
c[0][0] = data[0] & 0xf8;
|
||||
c[0][1] = data[1] & 0xf8;
|
||||
c[0][2] = data[2] & 0xf8;
|
||||
c[1][0] = c[0][0] + (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20);
|
||||
c[1][1] = c[0][1] + (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20);
|
||||
c[1][2] = c[0][2] + (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20);
|
||||
c[0][0] |= c[0][0] >> 5;
|
||||
c[0][1] |= c[0][1] >> 5;
|
||||
c[0][2] |= c[0][2] >> 5;
|
||||
c[1][0] |= c[1][0] >> 5;
|
||||
c[1][1] |= c[1][1] >> 5;
|
||||
c[1][2] |= c[1][2] >> 5;
|
||||
} else {
|
||||
c[0][0] = data[0] & 0xf0 | data[0] >> 4;
|
||||
c[1][0] = data[0] & 0x0f | data[0] << 4;
|
||||
c[0][1] = data[1] & 0xf0 | data[1] >> 4;
|
||||
c[1][1] = data[1] & 0x0f | data[1] << 4;
|
||||
c[0][2] = data[2] & 0xf0 | data[2] >> 4;
|
||||
c[1][2] = data[2] & 0x0f | data[2] << 4;
|
||||
}
|
||||
|
||||
uint_fast16_t j = data[6] << 8 | data[7];
|
||||
uint_fast16_t k = data[4] << 8 | data[5];
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
|
||||
void decode_etc1(const void *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint8_t *d = (uint8_t*)data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d += 8) {
|
||||
decode_etc1_block(d, buf);
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint_fast16_t j = data[6] << 8 | data[7];
|
||||
uint_fast16_t k = data[4] << 8 | data[5];
|
||||
uint_fast8_t c[3][3] = {};
|
||||
|
||||
if (data[3] & 2) {
|
||||
uint_fast8_t r = data[0] & 0xf8;
|
||||
int_fast16_t dr = (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20);
|
||||
uint_fast8_t g = data[1] & 0xf8;
|
||||
int_fast16_t dg = (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20);
|
||||
uint_fast8_t b = data[2] & 0xf8;
|
||||
int_fast16_t db = (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20);
|
||||
if (r + dr < 0 || r + dr > 255) {
|
||||
// T
|
||||
c[0][0] = data[0] << 3 & 0xc0 | data[0] << 4 & 0x30 | data[0] >> 1 & 0xc | data[0] & 3;
|
||||
c[0][1] = data[1] & 0xf0 | data[1] >> 4;
|
||||
c[0][2] = data[1] & 0x0f | data[1] << 4;
|
||||
c[1][0] = data[2] & 0xf0 | data[2] >> 4;
|
||||
c[1][1] = data[2] & 0x0f | data[2] << 4;
|
||||
c[1][2] = data[3] & 0xf0 | data[3] >> 4;
|
||||
uint_fast8_t d = Etc2DistanceTable[data[3] >> 1 & 6 | data[3] & 1];
|
||||
uint_fast32_t color_set[4] = {
|
||||
applicate_color_raw(c[0]),
|
||||
applicate_color(c[1], d),
|
||||
applicate_color_raw(c[1]),
|
||||
applicate_color(c[1], -d)
|
||||
};
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
outbuf[WriteOrderTable[i]] = color_set[k << 1 & 2 | j & 1];
|
||||
} else if (g + dg < 0 || g + dg > 255) {
|
||||
// H
|
||||
c[0][0] = data[0] << 1 & 0xf0 | data[0] >> 3 & 0xf;
|
||||
c[0][1] = data[0] << 5 & 0xe0 | data[1] & 0x10;
|
||||
c[0][1] |= c[0][1] >> 4;
|
||||
c[0][2] = data[1] & 8 | data[1] << 1 & 6 | data[2] >> 7;
|
||||
c[0][2] |= c[0][2] << 4;
|
||||
c[1][0] = data[2] << 1 & 0xf0 | data[2] >> 3 & 0xf;
|
||||
c[1][1] = data[2] << 5 & 0xe0 | data[3] >> 3 & 0x10;
|
||||
c[1][1] |= c[1][1] >> 4;
|
||||
c[1][2] = data[3] << 1 & 0xf0 | data[3] >> 3 & 0xf;
|
||||
uint_fast8_t d = data[3] & 4 | data[3] << 1 & 2;
|
||||
if (c[0][0] > c[1][0] || (c[0][0] == c[1][0] && (c[0][1] > c[1][1] || (c[0][1] == c[1][1] && c[0][2] >= c[1][2]))))
|
||||
++d;
|
||||
d = Etc2DistanceTable[d];
|
||||
uint_fast32_t color_set[4] = {
|
||||
applicate_color(c[0], d),
|
||||
applicate_color(c[0], -d),
|
||||
applicate_color(c[1], d),
|
||||
applicate_color(c[1], -d)
|
||||
};
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
outbuf[WriteOrderTable[i]] = color_set[k << 1 & 2 | j & 1];
|
||||
} else if (b + db < 0 || b + db > 255) {
|
||||
// planar
|
||||
c[0][0] = data[0] << 1 & 0xfc | data[0] >> 5 & 3;
|
||||
c[0][1] = data[0] << 7 & 0x80 | data[1] & 0x7e | data[0] & 1;
|
||||
c[0][2] = data[1] << 7 & 0x80 | data[2] << 2 & 0x60 | data[2] << 3 & 0x18 | data[3] >> 5 & 4;
|
||||
c[0][2] |= c[0][2] >> 6;
|
||||
c[1][0] = data[3] << 1 & 0xf8 | data[3] << 2 & 4 | data[3] >> 5 & 3;
|
||||
c[1][1] = data[4] & 0xfe | data[4] >> 7;
|
||||
c[1][2] = data[4] << 7 & 0x80 | data[5] >> 1 & 0x7c;
|
||||
c[1][2] |= c[1][2] >> 6;
|
||||
c[2][0] = data[5] << 5 & 0xe0 | data[6] >> 3 & 0x1c | data[5] >> 1 & 3;
|
||||
c[2][1] = data[6] << 3 & 0xf8 | data[7] >> 5 & 0x6 | data[6] >> 4 & 1;
|
||||
c[2][2] = data[7] << 2 | data[7] >> 4 & 3;
|
||||
for (int x = 3, i = 0; x >= 0; x--) {
|
||||
for (int y = 3; y >= 0; y--, i++) {
|
||||
uint8_t r = clamp((x * (c[1][0] - c[0][0]) + y * (c[2][0] - c[0][0]) + 4 * c[0][0] + 2) >> 2);
|
||||
uint8_t g = clamp((x * (c[1][1] - c[0][1]) + y * (c[2][1] - c[0][1]) + 4 * c[0][1] + 2) >> 2);
|
||||
uint8_t b = clamp((x * (c[1][2] - c[0][2]) + y * (c[2][2] - c[0][2]) + 4 * c[0][2] + 2) >> 2);
|
||||
outbuf[i] = color(r, g, b, 255);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// differential
|
||||
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
|
||||
uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
c[0][0] = r | r >> 5;
|
||||
c[0][1] = g | g >> 5;
|
||||
c[0][2] = b | b >> 5;
|
||||
c[1][0] = r + dr;
|
||||
c[1][1] = g + dg;
|
||||
c[1][2] = b + db;
|
||||
c[1][0] |= c[1][0] >> 5;
|
||||
c[1][1] |= c[1][1] >> 5;
|
||||
c[1][2] |= c[1][2] >> 5;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// individual
|
||||
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
|
||||
uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
c[0][0] = data[0] & 0xf0 | data[0] >> 4;
|
||||
c[1][0] = data[0] & 0x0f | data[0] << 4;
|
||||
c[0][1] = data[1] & 0xf0 | data[1] >> 4;
|
||||
c[1][1] = data[1] & 0x0f | data[1] << 4;
|
||||
c[0][2] = data[2] & 0xf0 | data[2] >> 4;
|
||||
c[1][2] = data[2] & 0x0f | data[2] << 4;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void decode_etc2a8_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
if (data[1] & 0xf0) {
|
||||
uint_fast8_t mult = data[1] >> 4;
|
||||
int_fast8_t *table = Etc2AlphaModTable[data[1] & 0xf];
|
||||
uint_fast64_t l =
|
||||
data[7] | (uint_fast16_t)data[6] << 8 |
|
||||
(uint_fast32_t)data[5] << 16 | (uint_fast32_t)data[4] << 24 |
|
||||
(uint_fast64_t)data[3] << 32 | (uint_fast64_t)data[2] << 40;
|
||||
for (int i = 0; i < 16; i++, l >>= 3)
|
||||
((uint8_t*)(outbuf + WriteOrderTableRev[i]))[3] = clamp(data[0] + mult * table[l & 7]);
|
||||
} else {
|
||||
for (int i = 0; i < 16; i++)
|
||||
((uint8_t*)(outbuf + i))[3] = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
void decode_etc2(const void *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint8_t *d = (uint8_t*)data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d += 8) {
|
||||
decode_etc2_block(d, buf);
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decode_etc2a1(const void *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint8_t *d = (uint8_t*)data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d += 9) {
|
||||
decode_etc2_block(d + 1, buf);
|
||||
for (int i = 0; i < 16; i++)
|
||||
((uint8_t*)(buf + i))[3] = d[0];
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decode_etc2a8(const void *data, const int w, const int h, uint32_t *image) {
|
||||
int bcw = (w + 3) / 4;
|
||||
int bch = (h + 3) / 4;
|
||||
int clen_last = (w + 3) % 4 + 1;
|
||||
uint32_t buf[16];
|
||||
const uint8_t *d = (uint8_t*)data;
|
||||
for (int t = 0; t < bch; t++) {
|
||||
for (int s = 0; s < bcw; s++, d += 16) {
|
||||
decode_etc2_block(d + 8, buf);
|
||||
decode_etc2a8_block(d, buf);
|
||||
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
|
||||
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
|
||||
memcpy(image + y * w + s * 4, buf + i * 4, clen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef ETC_H
|
||||
#define ETC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void decode_etc1(const void*, const int, const int, uint32_t*);
|
||||
void decode_etc2(const void*, const int, const int, uint32_t*);
|
||||
void decode_etc2a1(const void*, const int, const int, uint32_t*);
|
||||
void decode_etc2a8(const void*, const int, const int, uint32_t*);
|
||||
|
||||
#endif /* end of include guard: ETC_H */
|
||||
@@ -0,0 +1,8 @@
|
||||
require 'mkmf'
|
||||
|
||||
append_cppflags('-std=c11')
|
||||
append_cppflags('-O3')
|
||||
append_cppflags('-Wall')
|
||||
append_cppflags('-Wextra')
|
||||
append_cppflags('-Wvla')
|
||||
create_makefile('mikunyan/decoders/native')
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <ruby.h>
|
||||
#include "rgb.h"
|
||||
#include "etc.h"
|
||||
#include "astc.h"
|
||||
#include "dxtc.h"
|
||||
|
||||
/*
|
||||
* Decode image from RGB565 binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] size width * height
|
||||
* @param [Boolean] big whether input data are big endian
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_rgb565(VALUE self, VALUE rb_data, VALUE size, VALUE big) {
|
||||
if (RSTRING_LEN(rb_data) < FIX2LONG(size) * 2)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint8_t *image = (uint8_t*)malloc(FIX2LONG(size) * 4);
|
||||
decode_rgb565((uint16_t*)RSTRING_PTR(rb_data), FIX2INT(size), RTEST(big), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(size) * 4);
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_etc1((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_etc2((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 Alpha1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2a1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 9)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_etc2a8((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 Alpha8 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2a8(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 16)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_etc2a8((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ASTC compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @param [Integer] bw block width
|
||||
* @param [Integer] bh block height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_astc(VALUE self, VALUE rb_data, VALUE w, VALUE h, VALUE bw, VALUE bh) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + FIX2LONG(bw) - 1) / FIX2LONG(bw)) * ((FIX2LONG(h) + FIX2LONG(bh) - 1) / FIX2LONG(bh)) * 16)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
const uint8_t *data = (uint8_t*)RSTRING_PTR(rb_data);
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_astc(data, FIX2INT(w), FIX2INT(h), FIX2INT(bw), FIX2INT(bh), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from DXT1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_dxt1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_dxt1((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from DXT5 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] w image width
|
||||
* @param [Integer] h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_dxt5(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
|
||||
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 16)
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
|
||||
decode_dxt5((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
|
||||
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
|
||||
free(image);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Init_native() {
|
||||
VALUE mMikunyan = rb_define_module("Mikunyan");
|
||||
VALUE mDecodeHelper = rb_define_module_under(mMikunyan, "DecodeHelper");
|
||||
rb_define_module_function(mDecodeHelper, "decode_rgb565", rb_decode_rgb565, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc1", rb_decode_etc1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2", rb_decode_etc2, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2a1", rb_decode_etc2a1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2a8", rb_decode_etc2a8, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_astc", rb_decode_astc, 5);
|
||||
rb_define_module_function(mDecodeHelper, "decode_dxt1", rb_decode_dxt1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_dxt5", rb_decode_dxt5, 3);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <stdint.h>
|
||||
|
||||
static inline int is_system_little() {
|
||||
int x = 1;
|
||||
return *(char*)&x == 1;
|
||||
}
|
||||
|
||||
void decode_rgb565(const uint16_t* data, const int size, const int is_big_endian, uint8_t* image) {
|
||||
const uint16_t *d = data;
|
||||
if (is_big_endian == is_system_little()) {
|
||||
uint8_t *p = image;
|
||||
for (int i = 0; i < size; i++, d++, p += 4) {
|
||||
uint_fast8_t r = *d & 0x00f8;
|
||||
uint_fast8_t g = (*d & 0x0007) << 5 | (*d & 0xe000) >> 11;
|
||||
uint_fast8_t b = (*d & 0x1f00) >> 5;
|
||||
p[0] = r | r >> 5;
|
||||
p[1] = g | g >> 6;
|
||||
p[2] = b | b >> 5;
|
||||
p[3] = 255;
|
||||
}
|
||||
} else {
|
||||
uint32_t *p = (uint32_t*)image;
|
||||
for (int i = 0; i < size; i++, d++, p++)
|
||||
*p = (*d & 0xf800) >> 8 | *d >> 13 | (*d & 0x7e0) << 5 | (*d & 0x60) << 3 | *d << 19 | (*d & 0x1c) << 14 | 0xff000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef RGB_H
|
||||
#define RGB_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void decode_rgb565(const uint16_t*, const int, const int, uint8_t*);
|
||||
|
||||
#endif /* end of include guard: RGB_H */
|
||||
@@ -6,3 +6,7 @@ require "mikunyan/binary_reader"
|
||||
require "mikunyan/object_value"
|
||||
require "mikunyan/type_tree"
|
||||
require "mikunyan/constants"
|
||||
|
||||
# Module for deserializing Unity Assets and AssetBundles
|
||||
module Mikunyan
|
||||
end
|
||||
|
||||
+169
-106
@@ -1,21 +1,133 @@
|
||||
module Mikunyan
|
||||
# Class for representing Unity Asset
|
||||
# @attr_reader [String] name Asset name
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [Integer] target_platform target platform number
|
||||
# @attr_reader [Symbol] endian data endianness (:little or :big)
|
||||
# @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
|
||||
# @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects
|
||||
# @attr_reader [Array<Integer>] add_ids ?
|
||||
# @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
|
||||
class Asset
|
||||
attr_accessor :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
||||
attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
||||
|
||||
# Struct for representing Asset class definition
|
||||
# @attr [Integer] class_id class ID
|
||||
# @attr [Integer,nil] script_id script ID
|
||||
# @attr [String] hash hash value (16 or 32 bytes)
|
||||
# @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
|
||||
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
|
||||
|
||||
# Struct for representing Asset object information
|
||||
# @attr [Integer] path_id path ID
|
||||
# @attr [Integer] offset data offset
|
||||
# @attr [Integer] size data size
|
||||
# @attr [Integer,nil] type_id type ID
|
||||
# @attr [Integer,nil] class_id class ID
|
||||
# @attr [Integer,nil] class_idx class definition index
|
||||
# @attr [Boolean] destroyed? destroyed or not
|
||||
# @attr [String] data binary data of object
|
||||
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
|
||||
|
||||
# Struct for representing Asset reference information
|
||||
# @attr [String] path path
|
||||
# @attr [String] guid GUID (16 bytes)
|
||||
# @attr [Integer] type ?
|
||||
# @attr [String] file_path Asset name
|
||||
Reference = Struct.new(:path, :guid, :type, :file_path)
|
||||
|
||||
# Load Asset from binary string
|
||||
# @param [String] bin binary data
|
||||
# @param [String] name Asset name
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.load(bin, name)
|
||||
r = Asset.new(name)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Load Asset from file
|
||||
# @param [String] file file name
|
||||
# @param [String] name Asset name (automatically generated if not specified)
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.file(file, name=nil)
|
||||
name = File.basename(name, '.*') unless name
|
||||
Asset.load(File.binread(file), name)
|
||||
end
|
||||
|
||||
# Returns list of all path IDs
|
||||
# @return [Array<Integer>] list of all path IDs
|
||||
def path_ids
|
||||
@objects.map{|e| e.path_id}
|
||||
end
|
||||
|
||||
# Returns list of containers
|
||||
# @return [Array<Hash>,nil] list of all containers
|
||||
def containers
|
||||
obj = parse_object(1)
|
||||
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
||||
obj.m_Container.value.map do |e|
|
||||
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
||||
end
|
||||
end
|
||||
|
||||
# Parse object of given path ID
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Mikunyan::ObjectValue,nil] parsed object
|
||||
def parse_object(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
type_tree = Asset.parse_type_tree(klass)
|
||||
return nil unless type_tree
|
||||
|
||||
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
||||
end
|
||||
|
||||
# Parse object of given path ID and simplify it
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Hash,nil] parsed object
|
||||
def parse_object_simple(path_id)
|
||||
Asset.object_simplify(parse_object(path_id))
|
||||
end
|
||||
|
||||
# Returns object type name string
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [String,nil] type name
|
||||
def object_type(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
||||
klass.type_tree.nodes[0].type
|
||||
elsif klass
|
||||
Mikunyan::CLASS_ID[klass.class_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@endian = :big
|
||||
end
|
||||
|
||||
def self.file(file, name)
|
||||
r = Asset.new(name)
|
||||
r.load(File.binread(file))
|
||||
r
|
||||
end
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
metadata_size = br.i32u
|
||||
@@ -108,116 +220,31 @@ module Mikunyan
|
||||
end
|
||||
end
|
||||
|
||||
def path_ids
|
||||
@objects.map{|e| e.path_id}
|
||||
end
|
||||
|
||||
def containers
|
||||
obj = parse_object(1)
|
||||
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
||||
obj.m_Container.value.map do |e|
|
||||
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
||||
end
|
||||
end
|
||||
|
||||
def parse_object(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
type_tree = Asset.parse_type_tree(klass)
|
||||
return nil unless type_tree
|
||||
|
||||
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
||||
end
|
||||
|
||||
def parse_object_simple(path_id)
|
||||
Asset.object_simplify(parse_object(path_id))
|
||||
end
|
||||
|
||||
def object_type(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
||||
klass.type_tree.nodes[0].type
|
||||
elsif klass
|
||||
Mikunyan::CLASS_ID[klass.class_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_type_tree(klass)
|
||||
return nil unless klass.type_tree
|
||||
nodes = klass.type_tree.nodes
|
||||
tree = {}
|
||||
stack = []
|
||||
nodes.each do |node|
|
||||
this = {:name => node.name, :node => node, :children => []}
|
||||
if node.depth == 0
|
||||
tree = this
|
||||
else
|
||||
stack[node.depth - 1][:children] << this
|
||||
end
|
||||
stack[node.depth] = this
|
||||
end
|
||||
tree
|
||||
end
|
||||
|
||||
def self.object_simplify(obj)
|
||||
if obj.class != ObjectValue
|
||||
obj
|
||||
elsif obj.type == 'pair'
|
||||
[object_simplify(obj['first']), object_simplify(obj['second'])]
|
||||
elsif obj.type == 'map' && obj.array?
|
||||
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
|
||||
elsif obj.value?
|
||||
object_simplify(obj.value)
|
||||
elsif obj.array?
|
||||
obj.value.map{|e| object_simplify(e)}
|
||||
else
|
||||
hash = {}
|
||||
obj.keys.each do |key|
|
||||
hash[key] = object_simplify(obj[key])
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_object_private(br, type_tree)
|
||||
r = nil
|
||||
node = type_tree[:node]
|
||||
children = type_tree[:children]
|
||||
|
||||
if node.array?
|
||||
data = []
|
||||
size = parse_object_private(br, children.find{|e| e[:name] == 'size'})
|
||||
data = nil
|
||||
size = parse_object_private(br, children.find{|e| e[:name] == 'size'}).value
|
||||
data_type_tree = children.find{|e| e[:name] == 'data'}
|
||||
size.value.times do |i|
|
||||
data << parse_object_private(br, data_type_tree)
|
||||
if node.type == 'TypelessData'
|
||||
data = br.read(size * data_type_tree[:node].size)
|
||||
else
|
||||
data = size.times.map{ parse_object_private(br, data_type_tree) }
|
||||
end
|
||||
data = data.map{|e| e.value}.pack('C*') if node.type == 'TypelessData'
|
||||
r = ObjectValue.new(node.name, node.type, br.endian, data)
|
||||
elsif node.size == -1
|
||||
r = ObjectValue.new(node.name, node.type, br.endian)
|
||||
if children.size == 1 && children[0][:name] == 'Array' && children[0][:node].type == 'Array' && children[0][:node].array?
|
||||
r.value = parse_object_private(br, children[0]).value
|
||||
r.value = r.value.map{|e| e.value}.pack('C*').force_encoding("utf-8") if node.type == 'string'
|
||||
if node.type == 'string'
|
||||
size = parse_object_private(br, children[0][:children].find{|e| e[:name] == 'size'}).value
|
||||
r.value = br.read(size * children[0][:children].find{|e| e[:name] == 'data'}[:node].size).force_encoding("utf-8")
|
||||
br.align(4) if children[0][:node].flags & 0x4000 != 0
|
||||
else
|
||||
r.value = parse_object_private(br, children[0]).value
|
||||
end
|
||||
else
|
||||
children.each do |child|
|
||||
r[child[:name]] = parse_object_private(br, child)
|
||||
@@ -230,7 +257,6 @@ module Mikunyan
|
||||
children.each do |child|
|
||||
r[child[:name]] = parse_object_private(br, child)
|
||||
end
|
||||
br.jmp(pos + node.size)
|
||||
else
|
||||
pos = br.pos
|
||||
value = nil
|
||||
@@ -268,5 +294,42 @@ module Mikunyan
|
||||
br.align(4) if node.flags & 0x4000 != 0
|
||||
r
|
||||
end
|
||||
|
||||
def self.object_simplify(obj)
|
||||
if obj.class != ObjectValue
|
||||
obj
|
||||
elsif obj.type == 'pair'
|
||||
[object_simplify(obj['first']), object_simplify(obj['second'])]
|
||||
elsif obj.type == 'map' && obj.array?
|
||||
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
|
||||
elsif obj.value?
|
||||
object_simplify(obj.value)
|
||||
elsif obj.array?
|
||||
obj.value.map{|e| object_simplify(e)}
|
||||
else
|
||||
hash = {}
|
||||
obj.keys.each do |key|
|
||||
hash[key] = object_simplify(obj[key])
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_type_tree(klass)
|
||||
return nil unless klass.type_tree
|
||||
nodes = klass.type_tree.nodes
|
||||
tree = {}
|
||||
stack = []
|
||||
nodes.each do |node|
|
||||
this = {:name => node.name, :node => node, :children => []}
|
||||
if node.depth == 0
|
||||
tree = this
|
||||
else
|
||||
stack[node.depth - 1][:children] << this
|
||||
end
|
||||
stack[node.depth] = this
|
||||
end
|
||||
tree
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
require 'extlz4'
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing Unity AssetBundle
|
||||
# @attr_reader [String] signature file signature (UnityRaw or UnityFS)
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] unity_version version string of Unity to use this AssetBundle
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [Array<Mikunyan::Asset>] assets included Assets
|
||||
class AssetBundle
|
||||
attr_accessor :signature, :format, :unity_version, :generator_version, :assets
|
||||
attr_reader :signature, :format, :unity_version, :generator_version, :assets
|
||||
|
||||
# Load AssetBundle from binary string
|
||||
# @param [String] bin binary data
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.load(bin)
|
||||
r = AssetBundle.new
|
||||
r.load(bin)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Load AssetBundle from file
|
||||
# @param [String] file file name
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.file(file)
|
||||
AssetBundle.load(File.binread(file))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
@signature = br.cstr
|
||||
@@ -31,8 +45,6 @@ module Mikunyan
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_unity_raw(br)
|
||||
@assets = []
|
||||
|
||||
@@ -48,8 +60,7 @@ module Mikunyan
|
||||
asset_size = br.i32u
|
||||
br.jmp(asset_pos + asset_header_size - 4)
|
||||
asset_data = br.read(asset_size)
|
||||
asset = Asset.new(asset_name)
|
||||
asset.load(asset_data)
|
||||
asset = Asset.load(asset_data, asset_name)
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
@@ -77,8 +88,7 @@ module Mikunyan
|
||||
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
|
||||
|
||||
asset_blocks.each do |b|
|
||||
asset = Asset.new(b[:name])
|
||||
asset.load(raw_data.byteslice(b[:offset], b[:size]))
|
||||
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name])
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
require 'bin_utils'
|
||||
|
||||
module Mikunyan
|
||||
# Class for manipulating binary string
|
||||
# @attr [Symbol] endian endianness
|
||||
# @attr [Integer] pos position
|
||||
# @attr [Integer] length data size
|
||||
class BinaryReader
|
||||
attr_accessor :endian, :pos, :length
|
||||
|
||||
# Constructor
|
||||
# @param [String] data binary string
|
||||
# @param [Symbol] endian endianness
|
||||
def initialize(data, endian = :big)
|
||||
@data = data
|
||||
@pos = 0
|
||||
@@ -11,104 +18,131 @@ module Mikunyan
|
||||
@endian = endian
|
||||
end
|
||||
|
||||
# Returns whether little endian or not
|
||||
# @return [Boolean]
|
||||
def little?
|
||||
@endian == :little
|
||||
end
|
||||
|
||||
def jmp(pos = 0)
|
||||
# Jump to given position
|
||||
# @param [Integer] pos position
|
||||
def jmp(pos=0)
|
||||
@pos = pos
|
||||
end
|
||||
|
||||
def adv(size = 0)
|
||||
# Advance position given size
|
||||
# @param [Integer] size size
|
||||
def adv(size=0)
|
||||
@pos += size
|
||||
end
|
||||
|
||||
# Round up position to multiple of given size
|
||||
# @param [Integer] size size
|
||||
def align(size)
|
||||
@pos = (@pos + size - 1) / size * size
|
||||
end
|
||||
|
||||
# Read given size of binary string and seek
|
||||
# @param [Integer] size size
|
||||
# @return [String] data
|
||||
def read(size)
|
||||
data = @data.byteslice(@pos, size)
|
||||
@pos += size
|
||||
data
|
||||
end
|
||||
|
||||
# Read string until null character
|
||||
# @return [String] string
|
||||
def cstr
|
||||
r = @data.unpack("@#{pos}Z*")[0]
|
||||
@pos += r.bytesize + 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 8bit signed integer
|
||||
def i8
|
||||
i8s
|
||||
end
|
||||
|
||||
# Read 8bit signed integer
|
||||
def i8s
|
||||
r = BinUtils.get_sint8(@data, @pos)
|
||||
@pos += 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 8bit unsigned integer
|
||||
def i8u
|
||||
r = BinUtils.get_int8(@data, @pos)
|
||||
@pos += 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 16bit signed integer
|
||||
def i16
|
||||
i16s
|
||||
end
|
||||
|
||||
# Read 16bit signed integer
|
||||
def i16s
|
||||
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
|
||||
@pos += 2
|
||||
r
|
||||
end
|
||||
|
||||
# Read 16bit unsigned integer
|
||||
def i16u
|
||||
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
|
||||
@pos += 2
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit signed integer
|
||||
def i32
|
||||
i32s
|
||||
end
|
||||
|
||||
# Read 32bit signed integer
|
||||
def i32s
|
||||
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit unsigned integer
|
||||
def i32u
|
||||
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit signed integer
|
||||
def i64
|
||||
i64s
|
||||
end
|
||||
|
||||
# Read 64bit signed integer
|
||||
def i64s
|
||||
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
|
||||
@pos += 8
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit unsigned integer
|
||||
def i64u
|
||||
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
|
||||
@pos += 8
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit floating point value
|
||||
def float
|
||||
r = little? ? @data.byteslice(@pos, 4).unpack('e')[0] : @data.byteslice(@pos, 4).unpack('g')[0]
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit floating point value
|
||||
def double
|
||||
r = little? ? @data.byteslice(@pos, 8).unpack('E')[0] : @data.byteslice(@pos, 8).unpack('G')[0]
|
||||
@pos += 8
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module Mikunyan
|
||||
private
|
||||
STRING_TABLE = {
|
||||
0=>'AABB',
|
||||
5=>'AnimationClip',
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
require 'mikunyan/decoders/image_decoder'
|
||||
|
||||
module Mikunyan
|
||||
# Module for helper classes for decoding object
|
||||
module DecodeHelper
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end
|
||||
require 'bin_utils'
|
||||
require 'mikunyan/decoders/native'
|
||||
|
||||
module Mikunyan
|
||||
# Class for image decoding tools
|
||||
class ImageDecoder
|
||||
Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]]
|
||||
Etc1SubblockTable = [[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1]]
|
||||
|
||||
# Decode image from Mikunyan::ObjectValue
|
||||
# @param [Mikunyan::ObjectValue] object object to decode
|
||||
# @return [ChunkyPNG::Image,nil] decoded image
|
||||
def self.decode_object(object)
|
||||
return nil unless object.class == ObjectValue
|
||||
|
||||
@@ -27,135 +29,448 @@ module Mikunyan
|
||||
when 2
|
||||
decode_argb4444(width, height, bin, endian)
|
||||
when 3
|
||||
decode_rgb888(width, height, bin, endian)
|
||||
decode_rgb24(width, height, bin)
|
||||
when 4
|
||||
decode_rgba8888(width, height, bin, endian)
|
||||
decode_rgba32(width, height, bin)
|
||||
when 5
|
||||
decode_argb8888(width, height, bin, endian)
|
||||
decode_argb32(width, height, bin)
|
||||
when 7
|
||||
decode_rgb565(width, height, bin, endian)
|
||||
when 9
|
||||
decode_r16(width, height, bin)
|
||||
when 10
|
||||
decode_dxt1(width, height, bin)
|
||||
when 12
|
||||
decode_dxt5(width, height, bin)
|
||||
when 13
|
||||
decode_rgba4444(width, height, bin, endian)
|
||||
when 14
|
||||
decode_bgra32(width, height, bin)
|
||||
when 15
|
||||
decode_rhalf(width, height, bin, endian)
|
||||
when 16
|
||||
decode_rghalf(width, height, bin, endian)
|
||||
when 17
|
||||
decode_rgbahalf(width, height, bin, endian)
|
||||
when 18
|
||||
decode_rfloat(width, height, bin, endian)
|
||||
when 19
|
||||
decode_rgfloat(width, height, bin, endian)
|
||||
when 20
|
||||
decode_rgbafloat(width, height, bin, endian)
|
||||
when 22
|
||||
decode_rgb9e5float(width, height, bin, endian)
|
||||
when 34
|
||||
decode_etc1(width, height, bin)
|
||||
when 45
|
||||
decode_etc2rgb(width, height, bin)
|
||||
when 46
|
||||
decode_etc2rgba1(width, height, bin)
|
||||
when 47
|
||||
decode_etc2rgba8(width, height, bin)
|
||||
when 48, 54
|
||||
decode_astc(width, height, 4, bin)
|
||||
when 49, 55
|
||||
decode_astc(width, height, 5, bin)
|
||||
when 50, 56
|
||||
decode_astc(width, height, 6, bin)
|
||||
when 51, 57
|
||||
decode_astc(width, height, 8, bin)
|
||||
when 52, 58
|
||||
decode_astc(width, height, 10, bin)
|
||||
when 53, 59
|
||||
decode_astc(width, height, 12, bin)
|
||||
when 62
|
||||
decode_rg16(width, height, bin)
|
||||
when 63
|
||||
decode_r8(width, height, bin)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode_etc1(width, height, bin)
|
||||
bw = (width + 3) / 4
|
||||
bh = (height + 3) / 4
|
||||
pixels = "\0" * (64 * bw * bh)
|
||||
pixels.force_encoding('ascii-8bit')
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
block = decode_etc1_block(BinUtils.get_sint64_be(bin, (bx + by * bw) * 8)).pack('N16')
|
||||
pixels[( bx * 4 * bh + by) * 16, 16] = block.byteslice( 0, 16)
|
||||
pixels[((bx * 4 + 1) * bh + by) * 16, 16] = block.byteslice(16, 16)
|
||||
pixels[((bx * 4 + 2) * bh + by) * 16, 16] = block.byteslice(32, 16)
|
||||
pixels[((bx * 4 + 3) * bh + by) * 16, 16] = block.byteslice(48, 16)
|
||||
end
|
||||
end
|
||||
ChunkyPNG::Image.from_rgba_stream(bh * 4, bw * 4, pixels).rotate_right!.crop!(0, 0, width, height)
|
||||
end
|
||||
|
||||
def self.decode_a8(width, height, bin)
|
||||
pixels = bin.unpack('C*').map do |c|
|
||||
c << 24 | c << 16 | c << 8 | 0xff
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
def self.decode_rgb565(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
r = (c & 0xf800) >> 8
|
||||
g = (c & 0x07e0) >> 3
|
||||
b = (c & 0x001f) << 3
|
||||
r = r | r >> 5
|
||||
g = g | g >> 6
|
||||
b = b | b >> 5
|
||||
r << 24 | g << 16 | b << 8 | 0xff
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
def self.decode_rgb888(width, height, bin, endian = :big)
|
||||
if endian == :little
|
||||
ChunkyPNG::Image.from_bgr_stream(width, height, bin)
|
||||
else
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, bin)
|
||||
end
|
||||
end
|
||||
|
||||
# Decode image from RGBA4444 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgba4444(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
(width * height).times do |i|
|
||||
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
||||
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
|
||||
c << 4 | c
|
||||
BinUtils.append_int32_be!(mem, c << 4 | c)
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from ARGB4444 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_argb4444(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
(width * height).times do |i|
|
||||
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
||||
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
|
||||
c << 4 | c
|
||||
BinUtils.append_int32_be!(mem, c << 4 | c)
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
def self.decode_rgba8888(width, height, bin, endian = :big)
|
||||
if endian == :little
|
||||
ChunkyPNG::Image.from_abgr_stream(width, height, bin)
|
||||
# Decode image from RGB565 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgb565(width, height, bin, endian = :big)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_rgb565(bin, width * height, endian == :big)).flip
|
||||
end
|
||||
|
||||
# Decode image from A8 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_a8(width, height, bin)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
c = BinUtils.get_int8(bin, i)
|
||||
BinUtils.append_int8!(mem, c, c, c)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from R8 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_r8(width, height, bin)
|
||||
decode_a8(width, height, bin)
|
||||
end
|
||||
|
||||
# Decode image from RG16 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rg16(width, height, bin)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i*2), 0)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RGB24 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgb24(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, bin).flip
|
||||
end
|
||||
|
||||
# Decode image from RGBA32 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgba32(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, bin).flip
|
||||
end
|
||||
|
||||
# Decode image from ARGB32 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_argb32(width, height, bin)
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
(width * height).times do |i|
|
||||
c = BinUtils.get_int32_be(bin, i*4)
|
||||
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
|
||||
end
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from BGRA32 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_bgra32(width, height, bin)
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
(width * height).times do |i|
|
||||
c = BinUtils.get_int32_le(bin, i*4)
|
||||
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
|
||||
end
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from R16 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_r16(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
||||
c = f2i(r / 65535.0)
|
||||
BinUtils.append_int8!(mem, c, c, c)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RGB9e5 binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgb9e5float(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
n = endian == :little ? BinUtils.get_int32_le(bin, i*4) : BinUtils.get_int32_be(bin, i*4)
|
||||
e = (n & 0xf8000000) >> 27
|
||||
r = (n & 0x7fc0000) >> 9
|
||||
g = (n & 0x3fe00) >> 9
|
||||
b = n & 0x1ff
|
||||
r = (r / 512r + 1) * (2**(e-15))
|
||||
g = (g / 512r + 1) * (2**(e-15))
|
||||
b = (b / 512r + 1) * (2**(e-15))
|
||||
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b))
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from R Half-float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rhalf(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
c = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)))
|
||||
BinUtils.append_int8!(mem, c, c, c)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RG Half-float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rghalf(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
(width * height).times do |i|
|
||||
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4) : BinUtils.get_int16_be(bin, i*4)))
|
||||
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4+2) : BinUtils.get_int16_be(bin, i*4+2)))
|
||||
BinUtils.append_int8!(mem, r, g, 0)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RGBA Half-float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgbahalf(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
(width * height).times do |i|
|
||||
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8) : BinUtils.get_int16_be(bin, i*8)))
|
||||
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+2) : BinUtils.get_int16_be(bin, i*8+2)))
|
||||
b = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+4) : BinUtils.get_int16_be(bin, i*8+4)))
|
||||
a = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+6) : BinUtils.get_int16_be(bin, i*8+6)))
|
||||
BinUtils.append_int8!(mem, r, g, b, a)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from R float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rfloat(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
unpackstr = endian == :little ? 'e' : 'g'
|
||||
(width * height).times do |i|
|
||||
c = f2i(bin.byteslice(i*4, 4).unpack(unpackstr)[0])
|
||||
BinUtils.append_int8!(mem, c, c, c)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RG float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgfloat(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 3)
|
||||
unpackstr = endian == :little ? 'e2' : 'g2'
|
||||
(width * height).times do |i|
|
||||
r, g = bin.byteslice(i*8, 8).unpack(unpackstr)
|
||||
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
|
||||
end
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from RGBA float binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @param [Symbol] endian endianness of binary
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_rgbafloat(width, height, bin, endian = :big)
|
||||
mem = String.new(capacity: width * height * 4)
|
||||
unpackstr = endian == :little ? 'e4' : 'g4'
|
||||
(width * height).times do |i|
|
||||
r, g, b, a = bin.byteslice(i*16, 16).unpack(unpackstr)
|
||||
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
|
||||
end
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
# Decode image from DXT1 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_dxt1(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt1(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from DXT5 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_dxt5(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt5(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from ETC1 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_etc1(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc1(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from ETC2 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_etc2rgb(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from ETC2 Alpha1 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_etc2rgba1(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a1(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from ETC2 Alpha8 compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_etc2rgba8(width, height, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a8(bin, width, height))
|
||||
end
|
||||
|
||||
# Decode image from ASTC compressed binary
|
||||
# @param [Integer] width image width
|
||||
# @param [Integer] height image height
|
||||
# @param [Integer] blocksize block size
|
||||
# @param [String] bin binary to decode
|
||||
# @return [ChunkyPNG::Image] decoded image
|
||||
def self.decode_astc(width, height, blocksize, bin)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_astc(bin, width, height, blocksize, blocksize))
|
||||
end
|
||||
|
||||
# Create ASTC file data from ObjectValue
|
||||
# @param [Mikunyan::ObjectValue,Hash] object target object
|
||||
# @return [String,nil] created file
|
||||
def self.create_astc_file(object)
|
||||
astc_list = {
|
||||
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
|
||||
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12
|
||||
}
|
||||
width = object['m_Width']
|
||||
height = object['m_Height']
|
||||
fmt = object['m_TextureFormat']
|
||||
bin = object['image data']
|
||||
width = width.value if width.class == ObjectValue
|
||||
height = height.value if height.class == ObjectValue
|
||||
fmt = fmt.value if fmt.class == ObjectValue
|
||||
bin = bin.value if bin.class == ObjectValue
|
||||
if width && height && fmt && astc_list[fmt]
|
||||
header = "\x13\xAB\xA1\x5C".force_encoding('ascii-8bit')
|
||||
header << [astc_list[fmt], astc_list[fmt], 1].pack("C*")
|
||||
header << [width].pack("V").byteslice(0, 3)
|
||||
header << [height].pack("V").byteslice(0, 3)
|
||||
header << "\x01\x00\x00"
|
||||
header + bin
|
||||
else
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, bin)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode_argb8888(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'V*' : 'N*').map do |c|
|
||||
c = ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24)
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.decode_etc1_block(bin)
|
||||
colors = []
|
||||
codes = [bin >> 37 & 7, bin >> 34 & 7]
|
||||
subblocks = Etc1SubblockTable[bin[32]]
|
||||
if bin[33] == 0
|
||||
colors[0] = bin >> 40 & 0xf0f0f0
|
||||
colors[0] = colors[0] | colors[0] >> 4
|
||||
colors[1] = bin >> 36 & 0xf0f0f0
|
||||
colors[1] = colors[1] | colors[1] >> 4
|
||||
# convert 16bit float
|
||||
def self.n2f(n)
|
||||
case n
|
||||
when 0x0000
|
||||
0.0
|
||||
when 0x8000
|
||||
-0.0
|
||||
when 0x7c00
|
||||
Float::INFINITY
|
||||
when 0xfc00
|
||||
-Float::INFINITY
|
||||
else
|
||||
colors[0] = bin >> 40 & 0xf8f8f8
|
||||
dr = (bin >> 56 & 3) - (bin >> 56 & 4)
|
||||
dg = (bin >> 48 & 3) - (bin >> 48 & 4)
|
||||
db = (bin >> 40 & 3) - (bin >> 40 & 4)
|
||||
colors[1] = colors[0] + (dr << 19) + (dg << 11) + (db << 3)
|
||||
colors[0] = colors[0] | (colors[0] >> 5 & 0x70707)
|
||||
colors[1] = colors[1] | (colors[1] >> 5 & 0x70707)
|
||||
s = n & 0x8000 != 0
|
||||
e = n & 0x7c00
|
||||
f = n & 0x03ff
|
||||
case e
|
||||
when 0x7c00
|
||||
Float::NAN
|
||||
when 0
|
||||
(s ? -f : f) * 2.0**-24
|
||||
else
|
||||
(s ? -1 : 1) * (f / 1024.0 + 1) * (2.0 ** ((e >> 10)-15))
|
||||
end
|
||||
end
|
||||
|
||||
ret = Array.new(16, 0)
|
||||
16.times do |i|
|
||||
modifier = Etc1ModifierTable[codes[subblocks[i]]][bin[i]]
|
||||
ret[i] = etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def self.etc1colormod(color, modifier)
|
||||
r = (color >> 16 & 0xff) + modifier
|
||||
g = (color >> 8 & 0xff) + modifier
|
||||
b = (color & 0xff) + modifier
|
||||
r = (r > 255 ? 255 : r < 0 ? 0 : r)
|
||||
g = (g > 255 ? 255 : g < 0 ? 0 : g)
|
||||
b = (b > 255 ? 255 : b < 0 ? 0 : b)
|
||||
r << 24 | g << 16 | b << 8 | 0xff
|
||||
# [0.0,1.0] -> [0,255]
|
||||
def self.f2i(d)
|
||||
(d * 255).round.clamp(0, 255)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
module Mikunyan
|
||||
# Class for representing decoded object
|
||||
# @attr [String] name object name
|
||||
# @attr [String] type object type name
|
||||
# @attr [Object] value object
|
||||
# @attr [Symbol] endian endianness
|
||||
# @attr [Boolean] is_struct
|
||||
class ObjectValue
|
||||
attr_accessor :name, :type, :value, :endian, :is_struct
|
||||
|
||||
# Constructor
|
||||
# @param [String] name object name
|
||||
# @param [String] type object type name
|
||||
# @param [Symbol] endian endianness
|
||||
# @param [Object] value object
|
||||
def initialize(name, type, endian, value = nil)
|
||||
@name = name
|
||||
@type = type
|
||||
@@ -11,46 +22,70 @@ module Mikunyan
|
||||
@attr = {}
|
||||
end
|
||||
|
||||
# Return whether object is array or not
|
||||
# @return [Boolean]
|
||||
def array?
|
||||
value && value.class == Array
|
||||
end
|
||||
|
||||
# Return whether object is value or not
|
||||
# @return [Boolean]
|
||||
def value?
|
||||
value && value.class != Array
|
||||
end
|
||||
|
||||
# Return whether object is struct or not
|
||||
# @return [Boolean]
|
||||
def struct?
|
||||
is_struct
|
||||
end
|
||||
|
||||
# Return all keys
|
||||
# @return [Array] list of keys
|
||||
def keys
|
||||
@attr.keys
|
||||
end
|
||||
|
||||
# Return whether object contains key
|
||||
# @param [String] key
|
||||
# @return [Boolean]
|
||||
def key?(key)
|
||||
@attr.key?(key)
|
||||
end
|
||||
|
||||
# Return value
|
||||
# @return [Object] value
|
||||
def []
|
||||
@value
|
||||
end
|
||||
|
||||
def [](name)
|
||||
if array? && name.class == Integer
|
||||
@value[name]
|
||||
# Return value of selected index or key
|
||||
# @param [Integer,String] i index or key
|
||||
# @return [Object] value
|
||||
def [](i)
|
||||
if array? && i.class == Integer
|
||||
@value[i]
|
||||
else
|
||||
@attr[name]
|
||||
@attr[i]
|
||||
end
|
||||
end
|
||||
|
||||
# Set value of selected key
|
||||
# @param [String] name key
|
||||
# @param [Object] value value
|
||||
# @return [Object] value
|
||||
def []=(name, value)
|
||||
@attr[name] = value
|
||||
end
|
||||
|
||||
# Return value of called key
|
||||
# @param [String] name key
|
||||
# @return [Object] value
|
||||
def method_missing(name, *args)
|
||||
@attr[name.to_s]
|
||||
end
|
||||
|
||||
# Implementation of respond_to_missing?
|
||||
def respond_to_missing?(symbol, include_private)
|
||||
@attr.key?(symbol.to_s)
|
||||
end
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
module Mikunyan
|
||||
# Class for representing TypeTree
|
||||
# @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes
|
||||
class TypeTree
|
||||
attr_accessor :nodes
|
||||
|
||||
# Struct for representing Node in TypeTree
|
||||
# @attr [String] version version string
|
||||
# @attr [Integer] depth depth of node (>= 0)
|
||||
# @attr [Boolean] array? array node or not
|
||||
# @attr [String] type type name
|
||||
# @attr [String] name node (attribute) name
|
||||
# @attr [Integer] index index in node list
|
||||
# @attr [Integer] flags flags of node
|
||||
Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags)
|
||||
|
||||
# Create TypeTree from binary string (new version)
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @return [Mikunyan::TypeTree] created TypeTree
|
||||
def self.load(br)
|
||||
nodes = []
|
||||
node_count = br.i32u
|
||||
@@ -28,6 +42,9 @@ module Mikunyan
|
||||
r
|
||||
end
|
||||
|
||||
# Create TypeTree from binary string (legacy version)
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @return [Mikunyan::TypeTree] created TypeTree
|
||||
def self.load_legacy(br)
|
||||
nodes = []
|
||||
stack = [0]
|
||||
@@ -49,6 +66,9 @@ module Mikunyan
|
||||
r
|
||||
end
|
||||
|
||||
# Create default TypeTree from hash string (if exists)
|
||||
# @param [String] hash
|
||||
# @return [Mikunyan::TypeTree,nil] created TypeTree
|
||||
def self.load_default(hash)
|
||||
hash_str = hash.unpack('H*')[0]
|
||||
file = File.expand_path("../typetrees/#{hash_str}.dat", __FILE__)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,4 @@
|
||||
module Mikunyan
|
||||
VERSION = "3.9.0"
|
||||
# version string
|
||||
VERSION = "3.9.5"
|
||||
end
|
||||
|
||||
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
spec.extensions = ["ext/decoders/native/extconf.rb"]
|
||||
|
||||
spec.add_dependency 'extlz4', '~> 0'
|
||||
spec.add_dependency 'bin_utils', '~> 0'
|
||||
|
||||
Reference in New Issue
Block a user