1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/decoder/astc_file.h"
16
17 #include <cstring>
18 #include <fstream>
19 #include <memory>
20 #include <sstream>
21
22 namespace astc_codec {
23
24 namespace {
25 static constexpr size_t kASTCHeaderSize = 16;
26
27 // Reads a value of size T from the buffer at the current offset, then
28 // increments the offset.
29 template<typename T>
ReadVal(const char * file_data,size_t & offset)30 inline T ReadVal(const char* file_data, size_t& offset) {
31 T x;
32 memcpy(&x, &file_data[offset], sizeof(T));
33 offset += sizeof(T);
34 return x;
35 }
36 } // namespace
37
ASTCFile(Header && header,std::string && blocks)38 ASTCFile::ASTCFile(Header&& header, std::string&& blocks)
39 : header_(std::move(header)), blocks_(std::move(blocks)) {}
40
LoadFromMemory(const char * data,size_t length,std::string * error)41 std::unique_ptr<ASTCFile> ASTCFile::LoadFromMemory(const char* data,
42 size_t length,
43 std::string* error) {
44 if (length < kASTCHeaderSize) {
45 *error = "Incomplete header.";
46 return nullptr;
47 }
48
49 base::Optional<Header> header_opt = ParseHeader(data);
50 if (!header_opt) {
51 *error = "Invalid ASTC header.";
52 return nullptr;
53 }
54
55 Header header = header_opt.value();
56
57 if (header.block_width_ == 0 || header.block_height_ == 0) {
58 *error = "Invalid block size.";
59 return nullptr;
60 }
61
62 std::string blocks(data + kASTCHeaderSize, data + length);
63
64 // Check that this file has the expected number of blocks.
65 const size_t expected_block_count =
66 ((header.width_ + header.block_width_ - 1) / header.block_width_) *
67 ((header.height_ + header.block_height_ - 1) / header.block_height_);
68
69 if (blocks.size() % PhysicalASTCBlock::kSizeInBytes != 0 ||
70 blocks.size() / PhysicalASTCBlock::kSizeInBytes != expected_block_count) {
71 std::stringstream ss;
72 ss << "Unexpected file length " << blocks.size() << " expected "
73 << kASTCHeaderSize +
74 expected_block_count * PhysicalASTCBlock::kSizeInBytes
75 << " bytes.";
76 *error = ss.str();
77 return nullptr;
78 }
79
80 return std::unique_ptr<ASTCFile>(
81 new ASTCFile(std::move(header), std::move(blocks)));
82 }
83
LoadFile(const std::string & path,std::string * error)84 std::unique_ptr<ASTCFile> ASTCFile::LoadFile(const std::string& path,
85 std::string* error) {
86 std::ifstream is(path, std::ios::binary);
87 if (!is) {
88 *error = "File not found: " + path;
89 return nullptr;
90 }
91
92 char header_data[kASTCHeaderSize] = {};
93 if (!is.read(header_data, kASTCHeaderSize)) {
94 *error = "Failed to load ASTC header.";
95 return nullptr;
96 }
97
98 base::Optional<Header> header_opt = ParseHeader(header_data);
99 if (!header_opt) {
100 *error = "Invalid ASTC header.";
101 return nullptr;
102 }
103
104 Header header = header_opt.value();
105
106 std::string blocks;
107 {
108 std::ostringstream ss;
109 ss << is.rdbuf();
110 blocks = ss.str();
111 }
112
113 // Check that this file has the expected number of blocks.
114 const size_t expected_block_count =
115 ((header.width_ + header.block_width_ - 1) / header.block_width_) *
116 ((header.height_ + header.block_height_ - 1) / header.block_height_);
117
118 if (blocks.size() % PhysicalASTCBlock::kSizeInBytes != 0 ||
119 blocks.size() / PhysicalASTCBlock::kSizeInBytes != expected_block_count) {
120 std::stringstream ss;
121 ss << "Unexpected file length " << blocks.size() << " expected "
122 << kASTCHeaderSize +
123 expected_block_count * PhysicalASTCBlock::kSizeInBytes
124 << " bytes.";
125 *error = ss.str();
126 return nullptr;
127 }
128
129 return std::unique_ptr<ASTCFile>(
130 new ASTCFile(std::move(header), std::move(blocks)));
131 }
132
GetFootprint() const133 base::Optional<Footprint> ASTCFile::GetFootprint() const {
134 return Footprint::FromDimensions(int(header_.block_width_), int(header_.block_height_));
135 }
136
GetFootprintString() const137 std::string ASTCFile::GetFootprintString() const {
138 std::stringstream footprint;
139 footprint << header_.block_width_ << "x" << header_.block_height_;
140 return footprint.str();
141 }
142
GetRawBlockData() const143 const std::string& ASTCFile::GetRawBlockData() const {
144 return blocks_;
145 }
146
GetBlock(size_t block_idx) const147 PhysicalASTCBlock ASTCFile::GetBlock(size_t block_idx) const {
148 const size_t sz = PhysicalASTCBlock::kSizeInBytes;
149 const size_t offset = PhysicalASTCBlock::kSizeInBytes * block_idx;
150 assert(offset <= blocks_.size() - sz);
151 return PhysicalASTCBlock(blocks_.substr(offset, sz));
152 }
153
ParseHeader(const char * header)154 base::Optional<ASTCFile::Header> ASTCFile::ParseHeader(const char* header) {
155 size_t offset = 0;
156 // TODO(google): Handle endianness.
157 const uint32_t magic = ReadVal<uint32_t>(header, offset);
158 if (magic != 0x5CA1AB13) {
159 return {};
160 }
161
162 const uint32_t block_width = ReadVal<uint8_t>(header, offset);
163 const uint32_t block_height = ReadVal<uint8_t>(header, offset);
164 const uint32_t block_depth = ReadVal<uint8_t>(header, offset);
165
166 uint32_t width = 0;
167 width |= ReadVal<uint8_t>(header, offset);
168 width |= ReadVal<uint8_t>(header, offset) << 8;
169 width |= ReadVal<uint8_t>(header, offset) << 16;
170
171 uint32_t height = 0;
172 height |= ReadVal<uint8_t>(header, offset);
173 height |= ReadVal<uint8_t>(header, offset) << 8;
174 height |= ReadVal<uint8_t>(header, offset) << 16;
175
176 uint32_t depth = 0;
177 depth |= ReadVal<uint8_t>(header, offset);
178 depth |= ReadVal<uint8_t>(header, offset) << 8;
179 depth |= ReadVal<uint8_t>(header, offset) << 16;
180 assert(offset == kASTCHeaderSize);
181
182 return Header(width, height, depth, block_width, block_height, block_depth);
183 }
184
185 } // namespace astc_codec
186