Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions src/textord/imagefind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,67 @@ const int kMinImageFindSize = 100;
// will fatten out too much and have to be clipped to text.
const int kNoisePadding = 4;

// Cross-platform popcount implementation
// Uses compiler intrinsics when available for optimal performance
#if defined(__GNUC__) || defined(__clang__)
// GCC and Clang: use __builtin_popcount
#define POPCOUNT(x) __builtin_popcount(x)
#elif defined(_MSC_VER)
// MSVC: use __popcnt intrinsic (requires SSE4.2)
#include <intrin.h>
#define POPCOUNT(x) __popcnt(x)
#else
// Fallback: software implementation using bit manipulation
static inline int softwarePopcount(uint32_t x) {
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F;
x = x + (x >> 8);
x = x + (x >> 16);
return x & 0x3F;
}
#define POPCOUNT(x) softwarePopcount(x)
#endif

// Count set bits in range [x_start, x_end) using POPCNT instruction.
// Uses hardware popcount for 10-20x speedup vs bit-by-bit counting.
static inline int countBitsInRange(const uint32_t *line, int x_start, int x_end) {
int count = 0;
int word_start = x_start / 32;
int word_end = x_end / 32;
int bit_start = x_start % 32;
int bit_end = x_end % 32;

if (word_start == word_end) {
// Range within single word - avoid UB when shifting by 32
int num_bits = x_end - x_start;
uint32_t mask = (1U << num_bits) - 1;

mask <<= (32 - bit_start - num_bits);
count = POPCOUNT(line[word_start] & mask);
} else {
// First partial word
if (bit_start != 0) {
uint32_t mask = 0xFFFFFFFF >> bit_start;
count += POPCOUNT(line[word_start] & mask);
word_start++;
}

// Full words in middle
for (int w = word_start; w < word_end; ++w) {
count += POPCOUNT(line[w]);
}

// Last partial word (bit_end==0 means x_end on word boundary, skip it)
if (bit_end != 0) {
uint32_t mask = 0xFFFFFFFF << (32 - bit_end);
count += POPCOUNT(line[word_end] & mask);
}
}

return count;
}

// Scans horizontally on x=[x_start,x_end), starting with y=*y_start,
// stepping y+=y_step, until y=y_end. *ystart is input/output.
// If the number of black pixels in a row, pix_count fits this pattern:
Expand All @@ -62,15 +123,8 @@ static bool HScanForEdge(uint32_t *data, int wpl, int x_start, int x_end, int mi
int mid_width, int max_count, int y_end, int y_step, int *y_start) {
int mid_rows = 0;
for (int y = *y_start; y != y_end; y += y_step) {
// Need pixCountPixelsInRow(pix, y, &pix_count, nullptr) to count in a
// subset.
int pix_count = 0;
uint32_t *line = data + wpl * y;
for (int x = x_start; x < x_end; ++x) {
if (GET_DATA_BIT(line, x)) {
++pix_count;
}
}
int pix_count = countBitsInRange(line, x_start, x_end);
if (mid_rows == 0 && pix_count < min_count) {
continue; // In the min phase.
}
Expand Down Expand Up @@ -101,8 +155,11 @@ static bool VScanForEdge(uint32_t *data, int wpl, int y_start, int y_end, int mi
for (int x = *x_start; x != x_end; x += x_step) {
int pix_count = 0;
uint32_t *line = data + y_start * wpl;
// Vertical scan: cache bit position calculations
int word_offset = x / 32;
int bit_offset = 31 - (x & 31);
for (int y = y_start; y < y_end; ++y, line += wpl) {
if (GET_DATA_BIT(line, x)) {
if ((line[word_offset] >> bit_offset) & 1) {
++pix_count;
}
}
Expand Down
Loading