blob: 6066f275e026bd1185f126298155dc861291ecdb [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Defines a hash map for LZW-like algorithm.
//
// Author: Felipe Mourad Pereira (mouradfelipe@google.com)
#include "src/utils/hash_map.h"
#include <cassert>
#include <cstdint>
#include "src/dsp/math.h"
#include "src/enc/lossless/palette.h"
#include "src/utils/hash.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
namespace WP2 {
SegmentMap::SegmentMap(uint32_t num_segments)
: segments_capacity_(
num_segments +
1), // it has a +1 due to 0-index reserved for clear code.
num_bits_(WP2Log2Ceil_k(2 * segments_capacity_)),
num_colors_(0) {}
void SegmentMap::Clear() {
assert(num_colors_ < segments_.size());
// Segments in [1;num_colors_] are pure colors, they shouldn't be cleared.
segments_.resize_no_check(num_colors_ + 1);
for (uint32_t& segment_index : hash_map_) {
if (segment_index > num_colors_) {
segment_index = 0;
}
}
}
WP2Status SegmentMap::Allocate() {
WP2_CHECK_ALLOC_OK(segments_.reserve(segments_capacity_));
WP2_CHECK_ALLOC_OK(hash_map_.resize(1 << num_bits_));
segments_.push_back_no_resize({nullptr, 0}); // index 0 is for clear code
return WP2_STATUS_OK;
}
WP2Status SegmentMap::InsertColors(const WP2L::Palette& palette) {
ColorSegment seg;
seg.num_pixels = 1;
seg.data = &palette.GetColors()[0];
for (uint32_t i = 0; i < palette.Size(); ++i, seg.data += 4) {
assert(segments_.size() < segments_capacity_);
uint32_t index; // where to insert the segment in hash_map_
if (HasKey(seg, index)) {
// Color is duplicate.
assert(false);
}
hash_map_[index] = segments_.size();
segments_.push_back_no_resize(seg);
}
num_colors_ = segments_.size() - 1;
return WP2_STATUS_OK;
}
bool SegmentMap::HasKey(const ColorSegment& segment, uint32_t& index) const {
if (segment.num_pixels == 0) return false;
const uint32_t hash =
decoding::HashPixN(segment.data, segment.num_pixels, num_bits_);
uint32_t i = hash;
do {
if (hash_map_[i] == 0) break; // make sure it is not an empty position
if (segments_[hash_map_[i]] == segment) {
index = hash_map_[i];
return true;
}
if (++i == hash_map_.size()) i = 0;
} while (i != hash);
index = i; // this value is the position to add a new segment to hash_map_.
return false;
}
WP2Status SegmentMap::FindOrAdd(const ColorSegment& segment, uint32_t& index) {
WP2_CHECK_OK(segment.num_pixels != 0, WP2_STATUS_BAD_READ);
if (HasKey(segment, index)) {
return WP2_STATUS_OK;
}
if (segments_.size() == segments_capacity_) {
// 'segments_' is full.
// Request a clear cache.
index = kClearCode;
return WP2_STATUS_OK;
}
// If code reaches here, it means that 'index' is actually the index
// in hash_map_ to insert the element.
hash_map_[index] = segments_.size();
index = segments_.size();
segments_.push_back_no_resize(segment);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
// SegmentCache.
WP2Status SegmentCache::Allocate(uint32_t num_bits) {
num_bits_ = num_bits;
const uint64_t hash_size = (1 << num_bits_);
WP2_CHECK_ALLOC_OK(segments_.resize(hash_size));
return WP2_STATUS_OK;
}
bool SegmentCache::HasKey(const ColorSegment& segment,
uint32_t* const index) const {
assert(segment.num_pixels != 0);
const uint32_t hash =
decoding::HashPixN(segment.data, segment.num_pixels, num_bits_);
if (segments_[hash] == segment) {
if (index != nullptr) *index = hash;
return true;
} else {
return false;
}
}
bool SegmentCache::Insert(const ColorSegment& segment, uint32_t* const index) {
const uint32_t hash =
decoding::HashPixN(segment.data, segment.num_pixels, num_bits_);
if (index != nullptr) *index = hash;
if (segments_[hash] == segment) {
return false;
} else {
segments_[hash] = segment;
return true;
}
}
void SegmentCache::Lookup(uint32_t index, ColorSegment* const segment) const {
assert((index >> num_bits_) == 0u);
*segment = segments_[index];
}
} // namespace WP2