// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// Image_impl.hpp — Image<T> メソッド実装
//
// Image.hpp から自動インクルードされます。直接インクルードしないでください。
// 明示的インスタンス化: src/image/Image.cpp (float, double)

#ifndef CALX_IMAGE_IMAGE_IMPL_HPP
#define CALX_IMAGE_IMAGE_IMPL_HPP

namespace calx::image {

// ── 変換 ──

template<typename T>
Image<T> Image<T>::toGray() const {
    assert(numChannels() >= 3);
    Image<T> result(height(), width());
    for (std::size_t y = 0; y < height(); ++y)
        for (std::size_t x = 0; x < width(); ++x)
            result(y, x) = T{0.299} * channels_[0](y, x)
                          + T{0.587} * channels_[1](y, x)
                          + T{0.114} * channels_[2](y, x);
    return result;
}

template<typename T>
Image<T> Image<T>::toRgb() const {
    assert(numChannels() == 1);
    Image<T> result(height(), width(), 3);
    for (std::size_t y = 0; y < height(); ++y)
        for (std::size_t x = 0; x < width(); ++x) {
            T v = channels_[0](y, x);
            result.at(y, x, 0) = v; result.at(y, x, 1) = v; result.at(y, x, 2) = v;
        }
    return result;
}

// ── αブレンド・距離ヘルパー ──

template<typename T>
void Image<T>::blendPixel(int px, int py, const std::vector<T>& color, T alpha) {
    if (py < 0 || py >= static_cast<int>(height()) ||
        px < 0 || px >= static_cast<int>(width()) || alpha <= T(0)) return;
    alpha = std::min(alpha, T(1));
    for (int c = 0; c < numChannels() && c < static_cast<int>(color.size()); ++c) {
        T& dst = channels_[c](py, px);
        dst = dst * (T(1) - alpha) + color[c] * alpha;
    }
}

template<typename T>
double Image<T>::pointLineSegDist(double px, double py,
                                   double ax, double ay, double bx, double by) {
    double dx = bx - ax, dy = by - ay, len2 = dx*dx + dy*dy;
    if (len2 < 1e-12) return std::hypot(px - ax, py - ay);
    double t = std::clamp(((px-ax)*dx + (py-ay)*dy) / len2, 0.0, 1.0);
    return std::hypot(px - (ax + t*dx), py - (ay + t*dy));
}

// ── drawLine + AA ──

template<typename T>
void Image<T>::drawLine(int x0, int y0, int x1, int y1,
                        const std::vector<T>& color, int thickness, const LineAAParams& aa) {
    switch (aa.method) {
    case LineAA::None:          drawLineBresenham(x0,y0,x1,y1,color,thickness); break;
    case LineAA::Wu:            drawLineWu(x0,y0,x1,y1,color); break;
    case LineAA::GuptaSproull:  drawLineGuptaSproull(x0,y0,x1,y1,color,thickness,aa.filterRadius); break;
    case LineAA::AreaSampling:  drawLineArea(x0,y0,x1,y1,color,thickness); break;
    case LineAA::SuperSampling: drawLineSuperSample(x0,y0,x1,y1,color,thickness,aa.superSampleN); break;
    case LineAA::SDF:           drawLineSDF(x0,y0,x1,y1,color,thickness,aa.sdfSmooth); break;
    }
}

template<typename T>
void Image<T>::drawLineBresenham(int x0, int y0, int x1, int y1,
                                  const std::vector<T>& color, int thickness) {
    int half = thickness / 2;
    int dx = std::abs(x1-x0), dy = std::abs(y1-y0);
    int sx = (x0<x1)?1:-1, sy = (y0<y1)?1:-1, err = dx-dy;
    while (true) {
        for (int j = -half; j <= half; ++j)
            for (int i = -half; i <= half; ++i)
                blendPixel(x0+i, y0+j, color, T(1));
        if (x0==x1 && y0==y1) break;
        int e2 = 2*err;
        if (e2 > -dy) { err -= dy; x0 += sx; }
        if (e2 < dx)  { err += dx; y0 += sy; }
    }
}

template<typename T>
void Image<T>::drawLineWu(int x0, int y0, int x1, int y1, const std::vector<T>& color) {
    bool steep = std::abs(y1-y0) > std::abs(x1-x0);
    if (steep) { std::swap(x0,y0); std::swap(x1,y1); }
    if (x0 > x1) { std::swap(x0,x1); std::swap(y0,y1); }
    double dx=x1-x0, dy=y1-y0, grad=(dx==0)?1.0:dy/dx;
    auto plotWu = [&](int px, int py, double a) {
        if (steep) blendPixel(py,px,color,T(a)); else blendPixel(px,py,color,T(a));
    };
    double xEnd=std::round(double(x0)), yEnd=y0+grad*(xEnd-x0);
    double xGap=1.0-(double(x0)+0.5-std::floor(double(x0)+0.5));
    int xPx1=int(xEnd), yPx1=int(std::floor(yEnd));
    plotWu(xPx1,yPx1,(1.0-(yEnd-std::floor(yEnd)))*xGap);
    plotWu(xPx1,yPx1+1,(yEnd-std::floor(yEnd))*xGap);
    double intery=yEnd+grad;
    xEnd=std::round(double(x1)); yEnd=y1+grad*(xEnd-x1);
    xGap=double(x1)+0.5-std::floor(double(x1)+0.5);
    int xPx2=int(xEnd), yPx2=int(std::floor(yEnd));
    plotWu(xPx2,yPx2,(1.0-(yEnd-std::floor(yEnd)))*xGap);
    plotWu(xPx2,yPx2+1,(yEnd-std::floor(yEnd))*xGap);
    for (int x=xPx1+1; x<xPx2; ++x) {
        int iY=int(std::floor(intery)); double f=intery-std::floor(intery);
        plotWu(x,iY,1.0-f); plotWu(x,iY+1,f);
        intery+=grad;
    }
}

template<typename T>
void Image<T>::drawLineGuptaSproull(int x0,int y0,int x1,int y1,
                                     const std::vector<T>& color,int thickness,double filterRadius) {
    double hW=thickness*0.5, R=hW+filterRadius;
    int mnX=std::max(0,std::min(x0,x1)-int(R)-1), mxX=std::min(int(width())-1,std::max(x0,x1)+int(R)+1);
    int mnY=std::max(0,std::min(y0,y1)-int(R)-1), mxY=std::min(int(height())-1,std::max(y0,y1)+int(R)+1);
    for (int py=mnY;py<=mxY;++py) for (int px=mnX;px<=mxX;++px) {
        double d=pointLineSegDist(px+.5,py+.5,x0+.5,y0+.5,x1+.5,y1+.5);
        if (d>R) continue;
        double a=(d<=hW)?1.0:std::exp(-2.0*((d-hW)/filterRadius)*((d-hW)/filterRadius));
        blendPixel(px,py,color,T(a));
    }
}

template<typename T>
void Image<T>::drawLineArea(int x0,int y0,int x1,int y1,
                             const std::vector<T>& color,int thickness) {
    drawLineSuperSample(x0,y0,x1,y1,color,thickness,8);
}

template<typename T>
void Image<T>::drawLineSuperSample(int x0,int y0,int x1,int y1,
                                    const std::vector<T>& color,int thickness,int N) {
    double hW=thickness*0.5, m=hW+1.0;
    int mnX=std::max(0,std::min(x0,x1)-int(m)-1), mxX=std::min(int(width())-1,std::max(x0,x1)+int(m)+1);
    int mnY=std::max(0,std::min(y0,y1)-int(m)-1), mxY=std::min(int(height())-1,std::max(y0,y1)+int(m)+1);
    double iN=1.0/N, iN2=iN*iN;
    for (int py=mnY;py<=mxY;++py) for (int px=mnX;px<=mxX;++px) {
        int cnt=0;
        for (int sj=0;sj<N;++sj) for (int si=0;si<N;++si)
            if (pointLineSegDist(px+(si+.5)*iN,py+(sj+.5)*iN,x0+.5,y0+.5,x1+.5,y1+.5)<=hW) ++cnt;
        if (cnt>0) blendPixel(px,py,color,T(cnt*iN2));
    }
}

template<typename T>
void Image<T>::drawLineSDF(int x0,int y0,int x1,int y1,
                            const std::vector<T>& color,int thickness,double smooth) {
    double hW=thickness*0.5, m=hW+smooth*2.0;
    int mnX=std::max(0,std::min(x0,x1)-int(m)-1), mxX=std::min(int(width())-1,std::max(x0,x1)+int(m)+1);
    int mnY=std::max(0,std::min(y0,y1)-int(m)-1), mxY=std::min(int(height())-1,std::max(y0,y1)+int(m)+1);
    for (int py=mnY;py<=mxY;++py) for (int px=mnX;px<=mxX;++px) {
        double d=pointLineSegDist(px+.5,py+.5,x0+.5,y0+.5,x1+.5,y1+.5)-hW;
        double t=std::clamp((d+smooth)/(2.0*smooth),0.0,1.0);
        double a=1.0-t*t*(3.0-2.0*t);
        if (a>1e-3) blendPixel(px,py,color,T(a));
    }
}

// ── drawCircle / drawEllipse / drawRect ──

template<typename T>
void Image<T>::drawCircle(int cx,int cy,int r,const std::vector<T>& color,
                           int thickness,const LineAAParams& aa,double sa,double ea) {
    drawEllipse(cx,cy,r,r,0,color,thickness,aa,sa,ea);
}

template<typename T>
void Image<T>::drawEllipse(int cx,int cy,int rx,int ry,double angle,
                            const std::vector<T>& color,int thickness,const LineAAParams& aa,
                            double startAngle,double endAngle) {
    constexpr double pi=3.14159265358979323846;
    double cA=std::cos(angle*pi/180.0),sA=std::sin(angle*pi/180.0);
    double sa=startAngle*pi/180.0,ea=endAngle*pi/180.0;
    bool full=(std::abs(endAngle-startAngle)>=359.99);
    if (aa.method==LineAA::None) {
        int nS=std::max(36,(rx+ry)*2);
        for (int i=0;i<nS;++i) {
            double t0=sa+(ea-sa)*double(i)/nS, t1=sa+(ea-sa)*double(i+1)/nS;
            auto ep=[&](double t)->std::pair<int,int>{
                double lx=rx*std::cos(t),ly=ry*std::sin(t);
                return{cx+int(cA*lx-sA*ly+.5),cy+int(sA*lx+cA*ly+.5)};};
            auto[x0,y0]=ep(t0); auto[x1,y1]=ep(t1);
            drawLineBresenham(x0,y0,x1,y1,color,thickness);
        }
    } else {
        double hW=thickness*.5,sm=(aa.method==LineAA::SDF)?aa.sdfSmooth:0.7;
        double mg=std::max(double(rx),double(ry))+hW+sm*2.0+2.0;
        int mnX=std::max(0,cx-int(mg)),mxX=std::min(int(width())-1,cx+int(mg));
        int mnY=std::max(0,cy-int(mg)),mxY=std::min(int(height())-1,cy+int(mg));
        for (int py=mnY;py<=mxY;++py) for (int px=mnX;px<=mxX;++px) {
            double dx=(px+.5)-cx,dy=(py+.5)-cy;
            double lx=cA*dx+sA*dy,ly=-sA*dx+cA*dy;
            if (!full){double pa=std::atan2(ly/(ry>0?ry:1),lx/(rx>0?rx:1));while(pa<sa)pa+=2*pi;if(pa>ea)continue;}
            double nx=lx/std::max(double(rx),1e-6),ny=ly/std::max(double(ry),1e-6);
            double r=std::sqrt(nx*nx+ny*ny);
            double gx=lx/(double(rx)*rx),gy=ly/(double(ry)*ry),gl=std::sqrt(gx*gx+gy*gy);
            double sd=(gl>1e-12)?(r-1.0)/gl:0.0, sd2=std::abs(sd)-hW;
            double t=std::clamp((sd2+sm)/(2.0*sm),0.0,1.0);
            double a=1.0-t*t*(3.0-2.0*t);
            if (a>1e-3) blendPixel(px,py,color,T(a));
        }
    }
}

template<typename T>
void Image<T>::drawRect(int x0,int y0,int x1,int y1,const std::vector<T>& color,
                         int thickness,const LineAAParams& aa,double angle) {
    if (std::abs(angle)<1e-6) {
        drawLine(x0,y0,x1,y0,color,thickness,aa); drawLine(x1,y0,x1,y1,color,thickness,aa);
        drawLine(x1,y1,x0,y1,color,thickness,aa); drawLine(x0,y1,x0,y0,color,thickness,aa);
    } else {
        constexpr double pi=3.14159265358979323846;
        double cx=(x0+x1)*.5,cy=(y0+y1)*.5,hw=(x1-x0)*.5,hh=(y1-y0)*.5;
        double cA=std::cos(angle*pi/180.0),sA=std::sin(angle*pi/180.0);
        auto rot=[&](double lx,double ly)->std::pair<int,int>{
            return{int(cx+cA*lx-sA*ly+.5),int(cy+sA*lx+cA*ly+.5)};};
        auto[ax,ay]=rot(-hw,-hh); auto[bx,by]=rot(hw,-hh);
        auto[ex,ey]=rot(hw,hh);   auto[dx,dy]=rot(-hw,hh);
        drawLine(ax,ay,bx,by,color,thickness,aa); drawLine(bx,by,ex,ey,color,thickness,aa);
        drawLine(ex,ey,dx,dy,color,thickness,aa); drawLine(dx,dy,ax,ay,color,thickness,aa);
    }
}

// ── drawText ──

template<typename T>
void Image<T>::drawText(int x,int y,const std::string& text,const std::vector<T>& color,int scale) {
    static const uint8_t f[][7]={
        {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
        {0x0E,0x11,0x01,0x06,0x08,0x10,0x1F},{0x0E,0x11,0x01,0x06,0x01,0x11,0x0E},
        {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
        {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
        {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
        {0x00,0x00,0x00,0x00,0x00,0x0C,0x0C},{0x00,0x00,0x00,0x00,0x00,0x00,0x00},
        {0x00,0x00,0x00,0x1F,0x00,0x00,0x00},{0x00,0x0C,0x0C,0x00,0x0C,0x0C,0x00}};
    int cx=x;
    for (char ch:text) {
        int idx=-1;
        if (ch>='0'&&ch<='9') idx=ch-'0';
        else if (ch=='.') idx=10; else if (ch==' ') idx=11;
        else if (ch=='-') idx=12; else if (ch==':') idx=13;
        if (idx>=0) for (int r=0;r<7;++r) for (int c=0;c<5;++c)
            if (f[idx][r]&(0x10>>c)) for (int sy=0;sy<scale;++sy) for (int sx=0;sx<scale;++sx)
                blendPixel(cx+c*scale+sx,y+r*scale+sy,color,T(1));
        cx+=6*scale;
    }
}

// ── DRW-1〜10 ──

template<typename T>
void Image<T>::drawPolyline(const std::vector<std::pair<int,int>>& pts,
                             const std::vector<T>& color,int thickness,const LineAAParams& aa,bool closed) {
    if (pts.size()<2) return;
    for (std::size_t i=0;i+1<pts.size();++i)
        drawLine(pts[i].first,pts[i].second,pts[i+1].first,pts[i+1].second,color,thickness,aa);
    if (closed&&pts.size()>=3)
        drawLine(pts.back().first,pts.back().second,pts[0].first,pts[0].second,color,thickness,aa);
}

template<typename T>
void Image<T>::drawPolygon(const std::vector<std::pair<int,int>>& pts,
                            const std::vector<T>& color,int thickness,const LineAAParams& aa,bool filled) {
    if (filled) fillPolygon(pts,color); else drawPolyline(pts,color,thickness,aa,true);
}

template<typename T>
void Image<T>::drawRoundedRect(int x0,int y0,int x1,int y1,int radius,
                                const std::vector<T>& color,int thickness,const LineAAParams& aa,bool filled) {
    if (x0>x1) std::swap(x0,x1); if (y0>y1) std::swap(y0,y1);
    int r=std::min(radius,std::min((x1-x0)/2,(y1-y0)/2));
    if (filled) {
        for (int y=y0+r;y<=y1-r;++y) for (int x=x0;x<=x1;++x) blendPixel(x,y,color,T(1));
        for (int y=y0;y<y0+r;++y) for (int x=x0+r;x<=x1-r;++x) blendPixel(x,y,color,T(1));
        for (int y=y1-r+1;y<=y1;++y) for (int x=x0+r;x<=x1-r;++x) blendPixel(x,y,color,T(1));
        for (int dy=-r;dy<=r;++dy) for (int dx=-r;dx<=r;++dx) if (dx*dx+dy*dy<=r*r) {
            if (dx<=0&&dy<=0) blendPixel(x0+r+dx,y0+r+dy,color,T(1));
            if (dx>=0&&dy<=0) blendPixel(x1-r+dx,y0+r+dy,color,T(1));
            if (dx<=0&&dy>=0) blendPixel(x0+r+dx,y1-r+dy,color,T(1));
            if (dx>=0&&dy>=0) blendPixel(x1-r+dx,y1-r+dy,color,T(1));
        }
    } else {
        drawLine(x0+r,y0,x1-r,y0,color,thickness,aa); drawLine(x1,y0+r,x1,y1-r,color,thickness,aa);
        drawLine(x1-r,y1,x0+r,y1,color,thickness,aa); drawLine(x0,y1-r,x0,y0+r,color,thickness,aa);
        drawEllipse(x0+r,y0+r,r,r,0,color,thickness,aa,180,270);
        drawEllipse(x1-r,y0+r,r,r,0,color,thickness,aa,270,360);
        drawEllipse(x1-r,y1-r,r,r,0,color,thickness,aa,0,90);
        drawEllipse(x0+r,y1-r,r,r,0,color,thickness,aa,90,180);
    }
}

template<typename T>
void Image<T>::drawBezier(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,
                           const std::vector<T>& color,int thickness,const LineAAParams& aa,int seg) {
    int px=x0,py=y0;
    for (int i=1;i<=seg;++i) {
        double t=double(i)/seg,u=1-t;
        int nx=int(u*u*u*x0+3*u*u*t*x1+3*u*t*t*x2+t*t*t*x3+.5);
        int ny=int(u*u*u*y0+3*u*u*t*y1+3*u*t*t*y2+t*t*t*y3+.5);
        drawLine(px,py,nx,ny,color,thickness,aa); px=nx;py=ny;
    }
}

template<typename T>
void Image<T>::drawQuadBezier(int x0,int y0,int x1,int y1,int x2,int y2,
                               const std::vector<T>& color,int thickness,const LineAAParams& aa,int seg) {
    int px=x0,py=y0;
    for (int i=1;i<=seg;++i) {
        double t=double(i)/seg,u=1-t;
        int nx=int(u*u*x0+2*u*t*x1+t*t*x2+.5), ny=int(u*u*y0+2*u*t*y1+t*t*y2+.5);
        drawLine(px,py,nx,ny,color,thickness,aa); px=nx;py=ny;
    }
}

template<typename T>
void Image<T>::drawCatmullRom(const std::vector<std::pair<int,int>>& pts,const std::vector<T>& color,
                               int thickness,const LineAAParams& aa,int sps) {
    if (pts.size()<4){drawPolyline(pts,color,thickness,aa);return;}
    for (std::size_t i=0;i+3<pts.size();++i) {
        auto[x0,y0]=pts[i];auto[x1,y1]=pts[i+1];auto[x2,y2]=pts[i+2];auto[x3,y3]=pts[i+3];
        int px=x1,py=y1;
        for (int s=1;s<=sps;++s){
            double t=double(s)/sps,t2=t*t,t3=t2*t;
            int nx=int(.5*((-x0+3*x1-3*x2+x3)*t3+(2*x0-5*x1+4*x2-x3)*t2+(-x0+x2)*t+2*x1)+.5);
            int ny=int(.5*((-y0+3*y1-3*y2+y3)*t3+(2*y0-5*y1+4*y2-y3)*t2+(-y0+y2)*t+2*y1)+.5);
            drawLine(px,py,nx,ny,color,thickness,aa);px=nx;py=ny;
        }
    }
}

template<typename T>
void Image<T>::fillCircle(int cx,int cy,int r,const std::vector<T>& color){fillEllipse(cx,cy,r,r,0,color);}

template<typename T>
void Image<T>::fillEllipse(int cx,int cy,int rx,int ry,double angle,const std::vector<T>& color) {
    constexpr double pi=3.14159265358979323846;
    double cA=std::cos(angle*pi/180),sA=std::sin(angle*pi/180);
    int mg=std::max(rx,ry)+1;
    for (int py=cy-mg;py<=cy+mg;++py) for (int px=cx-mg;px<=cx+mg;++px) {
        double dx=px+.5-cx,dy=py+.5-cy,lx=cA*dx+sA*dy,ly=-sA*dx+cA*dy;
        double nx=lx/std::max(double(rx),1e-6),ny=ly/std::max(double(ry),1e-6);
        if (nx*nx+ny*ny<=1.0) blendPixel(px,py,color,T(1));
    }
}

template<typename T>
void Image<T>::fillRect(int x0,int y0,int x1,int y1,const std::vector<T>& color,double angle) {
    if (std::abs(angle)<1e-6) {
        if (x0>x1)std::swap(x0,x1);if(y0>y1)std::swap(y0,y1);
        for (int y=std::max(0,y0);y<=std::min(int(height())-1,y1);++y)
            for (int x=std::max(0,x0);x<=std::min(int(width())-1,x1);++x)
                blendPixel(x,y,color,T(1));
    } else {
        constexpr double pi=3.14159265358979323846;
        double cx=(x0+x1)*.5,cy=(y0+y1)*.5,hw=(x1-x0)*.5,hh=(y1-y0)*.5;
        double cA=std::cos(angle*pi/180),sA=std::sin(angle*pi/180);
        auto rot=[&](double lx,double ly)->std::pair<int,int>{return{int(cx+cA*lx-sA*ly+.5),int(cy+sA*lx+cA*ly+.5)};};
        fillPolygon({rot(-hw,-hh),rot(hw,-hh),rot(hw,hh),rot(-hw,hh)},color);
    }
}

template<typename T>
void Image<T>::floodFill(int x,int y,const std::vector<T>& color,bool connect8) {
    if (x<0||x>=int(width())||y<0||y>=int(height())) return;
    std::vector<T> sc(numChannels());
    for (int c=0;c<numChannels();++c) sc[c]=channels_[c](y,x);
    bool same=true;
    for (int c=0;c<numChannels()&&c<int(color.size());++c)
        if (std::abs(double(sc[c])-double(color[c]))>1e-6){same=false;break;}
    if (same) return;
    auto eq=[&](int px,int py){for(int c=0;c<numChannels();++c)
        if(std::abs(double(channels_[c](py,px))-double(sc[c]))>1e-6)return false;return true;};
    std::vector<std::pair<int,int>> stk; stk.push_back({x,y});
    while (!stk.empty()) {
        auto[cx,cy]=stk.back();stk.pop_back();
        if (cx<0||cx>=int(width())||cy<0||cy>=int(height())) continue;
        if (!eq(cx,cy)) continue;
        blendPixel(cx,cy,color,T(1));
        stk.push_back({cx+1,cy});stk.push_back({cx-1,cy});
        stk.push_back({cx,cy+1});stk.push_back({cx,cy-1});
        if (connect8){stk.push_back({cx+1,cy+1});stk.push_back({cx-1,cy+1});
                      stk.push_back({cx+1,cy-1});stk.push_back({cx-1,cy-1});}
    }
}

template<typename T>
void Image<T>::drawArrow(int x0,int y0,int x1,int y1,const std::vector<T>& color,
                          int thickness,const LineAAParams& aa,double hL,double hA) {
    drawLine(x0,y0,x1,y1,color,thickness,aa);
    constexpr double pi=3.14159265358979323846;
    double a=std::atan2(double(y1-y0),double(x1-x0)),ha=hA*pi/180;
    drawLine(x1,y1,x1-int(hL*std::cos(a-ha)+.5),y1-int(hL*std::sin(a-ha)+.5),color,thickness,aa);
    drawLine(x1,y1,x1-int(hL*std::cos(a+ha)+.5),y1-int(hL*std::sin(a+ha)+.5),color,thickness,aa);
}

template<typename T>
void Image<T>::drawMarker(int x,int y,MarkerType type,int size,const std::vector<T>& color,
                           int thickness,const LineAAParams& aa) {
    int s=size/2;
    switch(type){
    case MarkerType::Cross:drawLine(x-s,y,x+s,y,color,thickness,aa);drawLine(x,y-s,x,y+s,color,thickness,aa);break;
    case MarkerType::X:drawLine(x-s,y-s,x+s,y+s,color,thickness,aa);drawLine(x+s,y-s,x-s,y+s,color,thickness,aa);break;
    case MarkerType::Diamond:drawPolyline({{x,y-s},{x+s,y},{x,y+s},{x-s,y}},color,thickness,aa,true);break;
    case MarkerType::Square:drawRect(x-s,y-s,x+s,y+s,color,thickness,aa);break;
    case MarkerType::Circle:drawCircle(x,y,s,color,thickness,aa);break;
    case MarkerType::Triangle:drawPolyline({{x,y-s},{x+s,y+s},{x-s,y+s}},color,thickness,aa,true);break;
    }
}

template<typename T>
void Image<T>::drawPie(int cx,int cy,int radius,double sa,double ea,const std::vector<T>& color,
                        int thickness,const LineAAParams& aa,bool filled) {
    constexpr double pi=3.14159265358979323846;
    double saR=sa*pi/180,eaR=ea*pi/180;
    int sx=cx+int(radius*std::cos(saR)+.5),sy=cy+int(radius*std::sin(saR)+.5);
    int ex=cx+int(radius*std::cos(eaR)+.5),ey=cy+int(radius*std::sin(eaR)+.5);
    if (filled){
        int nS=std::max(16,radius*2);
        for(int i=0;i<nS;++i){
            double a0=saR+(eaR-saR)*double(i)/nS,a1=saR+(eaR-saR)*double(i+1)/nS;
            fillPolygon({{cx,cy},{cx+int(radius*std::cos(a0)+.5),cy+int(radius*std::sin(a0)+.5)},
                                  {cx+int(radius*std::cos(a1)+.5),cy+int(radius*std::sin(a1)+.5)}},color);
        }
    } else {
        drawCircle(cx,cy,radius,color,thickness,aa,sa,ea);
        drawLine(cx,cy,sx,sy,color,thickness,aa); drawLine(cx,cy,ex,ey,color,thickness,aa);
    }
}

// ── fillPolygon ──

template<typename T>
void Image<T>::fillPolygon(const std::vector<std::pair<int,int>>& pts,const std::vector<T>& color) {
    if (pts.size()<3) return;
    int mnY=pts[0].second,mxY=pts[0].second;
    for (auto&p:pts){mnY=std::min(mnY,p.second);mxY=std::max(mxY,p.second);}
    mnY=std::max(mnY,0);mxY=std::min(mxY,int(height())-1);
    std::size_t n=pts.size();
    for (int y=mnY;y<=mxY;++y){
        std::vector<int> xs;
        for (std::size_t i=0,j=n-1;i<n;j=i++){
            int yi=pts[i].second,yj=pts[j].second;
            if ((yi>y)!=(yj>y)) xs.push_back(pts[i].first+(y-yi)*(pts[j].first-pts[i].first)/(yj-yi));
        }
        std::sort(xs.begin(),xs.end());
        for (std::size_t i=0;i+1<xs.size();i+=2){
            int xS=std::max(xs[i],0),xE=std::min(xs[i+1],int(width())-1);
            for (int x=xS;x<=xE;++x) blendPixel(x,y,color,T(1));
        }
    }
}

// ── DRW-15: 破線描画 ──

template<typename T>
void Image<T>::drawDashedLine(int x0, int y0, int x1, int y1,
                               const std::vector<T>& color, int thickness,
                               const LineAAParams& aa, const StrokeStyle& stroke) {
    if (stroke.dashPattern.empty()) {
        drawLine(x0, y0, x1, y1, color, thickness, aa);
        return;
    }

    double dx = x1 - x0, dy = y1 - y0;
    double totalLen = std::sqrt(dx*dx + dy*dy);
    if (totalLen < 1e-6) return;
    double ux = dx / totalLen, uy = dy / totalLen;

    double pos = stroke.dashOffset;
    int patIdx = 0;
    int patSize = static_cast<int>(stroke.dashPattern.size());
    bool drawing = true;

    while (pos < totalLen) {
        double segLen = stroke.dashPattern[patIdx % patSize];
        double endPos = std::min(pos + segLen, totalLen);

        if (drawing) {
            int sx = int(x0 + pos * ux + 0.5), sy = int(y0 + pos * uy + 0.5);
            int ex = int(x0 + endPos * ux + 0.5), ey = int(y0 + endPos * uy + 0.5);
            drawLine(sx, sy, ex, ey, color, thickness, aa);
        }

        pos = endPos;
        drawing = !drawing;
        ++patIdx;
    }
}

// ── DRW-22: サブピクセル座標 API ──

template<typename T>
void Image<T>::drawLineF(double x0, double y0, double x1, double y1,
                          const std::vector<T>& color, int thickness,
                          const LineAAParams& aa) {
    // SDF ベースなのでサブピクセル精度が自然に得られる
    double halfW = thickness * 0.5;
    double smooth = (aa.method == LineAA::SDF) ? aa.sdfSmooth : 0.7;
    double margin = halfW + smooth * 2.0;
    int mnX = std::max(0, int(std::min(x0,x1) - margin) - 1);
    int mxX = std::min(int(width())-1, int(std::max(x0,x1) + margin) + 1);
    int mnY = std::max(0, int(std::min(y0,y1) - margin) - 1);
    int mxY = std::min(int(height())-1, int(std::max(y0,y1) + margin) + 1);

    for (int py = mnY; py <= mxY; ++py)
        for (int px = mnX; px <= mxX; ++px) {
            double d = pointLineSegDist(px+0.5, py+0.5, x0, y0, x1, y1) - halfW;
            double t = std::clamp((d + smooth) / (2.0 * smooth), 0.0, 1.0);
            double alpha = 1.0 - t * t * (3.0 - 2.0 * t);
            if (alpha > 1e-3) blendPixel(px, py, color, T(alpha));
        }
}

template<typename T>
void Image<T>::drawCircleF(double cx, double cy, double radius,
                            const std::vector<T>& color, int thickness,
                            const LineAAParams& aa) {
    double halfW = thickness * 0.5;
    double smooth = (aa.method == LineAA::SDF) ? aa.sdfSmooth : 0.7;
    double margin = radius + halfW + smooth * 2.0;
    int mnX = std::max(0, int(cx - margin) - 1);
    int mxX = std::min(int(width())-1, int(cx + margin) + 1);
    int mnY = std::max(0, int(cy - margin) - 1);
    int mxY = std::min(int(height())-1, int(cy + margin) + 1);

    for (int py = mnY; py <= mxY; ++py)
        for (int px = mnX; px <= mxX; ++px) {
            double dx = px + 0.5 - cx, dy = py + 0.5 - cy;
            double d = std::sqrt(dx*dx + dy*dy) - radius;
            double sd = std::abs(d) - halfW;
            double t = std::clamp((sd + smooth) / (2.0 * smooth), 0.0, 1.0);
            double alpha = 1.0 - t * t * (3.0 - 2.0 * t);
            if (alpha > 1e-3) blendPixel(px, py, color, T(alpha));
        }
}

// ── リサイズ・ROI ──

template<typename T>
Image<T> Image<T>::resize(std::size_t newH,std::size_t newW) const {
    Image<T> r(newH,newW,numChannels());
    double sY=double(height())/newH,sX=double(width())/newW;
    for (int ch=0;ch<numChannels();++ch) for (std::size_t y=0;y<newH;++y) for (std::size_t x=0;x<newW;++x){
        double srcY=y*sY,srcX=x*sX;int y0=int(srcY),x0=int(srcX);
        int y1=std::min(y0+1,int(height())-1),x1=std::min(x0+1,int(width())-1);
        double fy=srcY-y0,fx=srcX-x0;
        r.at(y,x,ch)=T((1-fy)*(1-fx)*channels_[ch](y0,x0)+(1-fy)*fx*channels_[ch](y0,x1)
            +fy*(1-fx)*channels_[ch](y1,x0)+fy*fx*channels_[ch](y1,x1));
    }
    return r;
}

template<typename T>
Image<T> Image<T>::roi(std::size_t y,std::size_t x,std::size_t h,std::size_t w) const {
    assert(y+h<=height()&&x+w<=width());
    Image<T> r(h,w,numChannels());
    for (int ch=0;ch<numChannels();++ch) for (std::size_t ry=0;ry<h;++ry) for (std::size_t rx=0;rx<w;++rx)
        r.at(ry,rx,ch)=channels_[ch](y+ry,x+rx);
    return r;
}

} // namespace calx::image

#endif // CALX_IMAGE_IMAGE_IMPL_HPP
