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