#include #include #include #include #include #include #include #include #include #define bytes(value) reinterpret_cast(value) #pragma pack(push, 1) struct BMPFileHeader { uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; }; struct BMPInfoHeader { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; }; struct BGR { uint8_t b,g,r; }; #pragma pack(pop) struct vertex { float x,y; }; struct BGRHash { size_t operator()(const BGR& c) const noexcept { return (c.r<<16)|(c.g<<8)|c.b; } }; bool operator==(const BGR&a,const BGR&b){ return a.r==b.r && a.g==b.g && a.b==b.b; } BGR EMPTY_COLOR{255,255,255}; uint8_t* imageData; uint8_t* tempImageData; size_t width,height,rowSize; std::unordered_map>,BGRHash> pixelsPerProvincesPerState; inline BGR getPixelSafe(size_t r,size_t c){ if(r>=height||c>=width) return EMPTY_COLOR; return *reinterpret_cast( imageData + r*rowSize + c*3 ); } inline BGR getPixelSafeIndex(size_t i){ return getPixelSafe(i/width,i%width); } /* ================= FLOOD FILL ================= */ void FloodFill(){ size_t size=width*height; for(size_t i=0;i(tempImageData + (i/width)*rowSize + (i%width)*3)==EMPTY_COLOR) continue; BGR color=*reinterpret_cast(tempImageData + (i/width)*rowSize + (i%width)*3); std::vector prov; std::queue q; q.push(i); while(!q.empty()){ size_t n=q.front(); q.pop(); BGR& px=*reinterpret_cast(tempImageData + (n/width)*rowSize + (n%width)*3); if(px!=color) continue; px=EMPTY_COLOR; prov.push_back(n); if(n>=width) q.push(n-width); if(n+width&prov,BGR id){ for(size_t i:prov){ size_t r=i/width,c=i%width; if(getPixelSafe(r-1,c)!=id|| getPixelSafe(r+1,c)!=id|| getPixelSafe(r,c-1)!=id|| getPixelSafe(r,c+1)!=id) return i; } return std::numeric_limits::max(); } void FindContour(){ for(auto&[state,provs]:pixelsPerProvincesPerState){ for(auto&prov:provs){ if(prov.empty()) continue; BGR id=getPixelSafeIndex(prov[0]); size_t start=FindStart(prov,id); if(start==std::numeric_limits::max()) continue; std::vector contour; contour.push_back(start); size_t cur=start; size_t prev = (start % width > 0) ? start - 1 : start + 1; const size_t MAX=prov.size()*8; for(size_t i=0;i& prov : provs) { if(prov.size() < 3) continue; const size_t n = prov.size(); std::vector keep_point(n, true); std::stack> st; // в стек кладем первый и последний индекс st.push({0, n - 1}); while(!st.empty()) { auto [startIndex, endIndex] = st.top(); st.pop(); double dMax = 0.0; size_t index = startIndex; for(size_t i = startIndex + 1; i < endIndex; ++i) { if(keep_point[i]) { double d = PerpendicularDistance( prov[i], prov[startIndex], prov[endIndex], width ); if(d > dMax) { dMax = d; index = i; } } } if(dMax >= epsilon) { st.push({startIndex, index}); st.push({index, endIndex}); } else { for(size_t j = startIndex + 1; j < endIndex; ++j) keep_point[j] = false; } } // формируем итоговый список std::vector result; result.reserve(n); for(size_t i = 0; i < n; ++i) { if(keep_point[i]) result.push_back(prov[i]); } prov.swap(result); } } } struct Pt { int x, y; size_t id; }; inline Pt make_pt(size_t i, size_t width) { return { int(i % width), int(i / width), i }; } inline long long cross(const Pt& a, const Pt& b, const Pt& c) { return 1LL * (b.x - a.x) * (c.y - a.y) - 1LL * (b.y - a.y) * (c.x - a.x); } long long signed_area2(const std::vector& p) { long long a = 0; for (size_t i = 0; i < p.size(); ++i) { const auto& p0 = p[i]; const auto& p1 = p[(i + 1) % p.size()]; a += 1LL * p0.x * p1.y - 1LL * p1.x * p0.y; } return a; } bool point_in_triangle(const Pt& p, const Pt& a, const Pt& b, const Pt& c) { long long c1 = cross(a, b, p); long long c2 = cross(b, c, p); long long c3 = cross(c, a, p); return (c1 >= 0 && c2 >= 0 && c3 >= 0); } std::vector triangulate_prov(const std::vector& prov, size_t width) { std::vector poly; poly.reserve(prov.size()); for (size_t i : prov) poly.push_back(make_pt(i, width)); // CCW if (signed_area2(poly) < 0) std::reverse(poly.begin(), poly.end()); std::vector out; // a,b,c,a,b,c,... while (poly.size() > 3) { bool ear_found = false; for (size_t i = 0; i < poly.size(); ++i) { size_t i0 = (i + poly.size() - 1) % poly.size(); size_t i1 = i; size_t i2 = (i + 1) % poly.size(); const Pt& a = poly[i0]; const Pt& b = poly[i1]; const Pt& c = poly[i2]; if (cross(a, b, c) <= 0) continue; // не выпуклая bool inside = false; for (size_t j = 0; j < poly.size(); ++j) { if (j == i0 || j == i1 || j == i2) continue; if (point_in_triangle(poly[j], a, b, c)) { inside = true; break; } } if (!inside) { out.push_back(a.id); out.push_back(b.id); out.push_back(c.id); poly.erase(poly.begin() + i1); ear_found = true; break; } } if (!ear_found) { // плохой контур (самопересечение / коллинеарность) break; } } if (poly.size() == 3) { out.push_back(poly[0].id); out.push_back(poly[1].id); out.push_back(poly[2].id); } return out; } void Triangulate() { for(auto& [state, provs] : pixelsPerProvincesPerState) { for(std::vector& prov : provs) { if (prov.size() < 3) continue; prov = triangulate_prov(prov, width); } } } /* ================= OUTPUT ================= */ void GenVertices(std::ofstream&out){ uint32_t sc=pixelsPerProvincesPerState.size(); out.write(bytes(&sc),4); for(auto&[s,provs]:pixelsPerProvincesPerState){ out.write(bytes(&s),3); uint32_t pc=provs.size(); out.write(bytes(&pc),4); for(auto&p:provs){ uint32_t vc=p.size(); out.write(bytes(&vc),4); for(size_t i:p){ vertex v{float(i%width),float(i/width)}; out.write(bytes(&v),sizeof(v)); } } } } /* ================= MAIN ================= */ int main(int argc,char**argv){ std::ifstream fin(argv[1],std::ios::binary); std::ofstream fout(argv[2],std::ios::binary); BMPFileHeader fh; BMPInfoHeader ih; fin.read((char*)&fh,sizeof(fh)); fin.read((char*)&ih,sizeof(ih)); width=ih.biWidth; height=ih.biHeight; rowSize=(width*3+3)&~3; std::vector img(rowSize*height); std::vector tmp(rowSize*height); fin.seekg(fh.bfOffBits); fin.read((char*)img.data(),img.size()); fin.seekg(fh.bfOffBits); fin.read((char*)tmp.data(),tmp.size()); imageData=img.data(); tempImageData=tmp.data(); FloodFill(); FindContour(); Aproximate(1); Triangulate(); GenVertices(fout); }