Уменьшение размера блочного рисунка без того, чтобы один блок сражался за две вещи

#math #floating-point #drawing #scaling

#математика #с плавающей запятой #рисование #масштабирование

Вопрос:

Код, который я потерял из-за сбоя жесткого диска, однако более года спустя он все еще беспокоит меня об этом, и я действительно хочу получить ответ, чтобы я мог спать по ночам, не задумываясь об этом. Итак, я писал программное обеспечение для дефрагментации, и оно отображало кластеры. Проблема с кодом рисования — математика с плавающей запятой. Я недостаточно опытен, чтобы решить эту математическую задачу. Я мог рисовать кластеры без проблем, однако из-за математики с плавающей запятой последний пиксель боролся с первым пикселем нового кластера, когда я уменьшил его, чтобы он соответствовал окну. Я не могу понять, как это предотвратить. Я попытался округлить вниз и вверх, но это оставило пиксель либо пустым, либо перезаписало пиксель в последнем кластере, когда этого не должно быть, и т.д. Это должно выглядеть так. Не могу понять, как это сделал этот JkDefrag (с открытым исходным кодом), и у меня есть исходный код для этого! Это то, что не дает мне спать по ночам. Пожалуйста, помогите мне хорошо выспаться.

введите описание изображения здесь

Рисование кода из резервной копии:

 void DrawTESTClusters( HWND hWnd, const CMemoryDC amp;memoryDC, LONGLONG ClusterStart, LONGLONG ClusterCount, int color )
{
    LONG Width;
    LONG Height;
    LONGLONG x1;
    LONGLONG y1;
    //LONGLONG x2;
    //LONGLONG y2;
    HDC hDC;
    LONG MaxLength;

    //DebugPrint(_T("%I64d - %I64d, %I64dn"), ClusterStart, ClusterEnd, ClusterEnd - ClusterStart);

    // The usual checks
    if ( TotalClusters <= 0 ) // Can this happen ??
        return;
    //_ASSERT( ClusterStart != ClusterEnd );
    //if ( ClusterStart == ClusterEnd ) // Can this happen ??
    //  return;
    _ASSERT( ClusterStart >= 0 amp;amp; ClusterStart <= TotalClusters );
    //if ( ClusterStart < 0 || ClusterStart > TotalClusters ) // Can this happen ??
    //  return;
    //_ASSERT( ClusterEnd >= 0 amp;amp; ClusterEnd <= TotalClusters );
    //if ( ClusterEnd < 0 || ClusterEnd > TotalClusters ) // Can this happen ??
    //  return;
    _ASSERT( ClusterCount >= 0 amp;amp; (ClusterStart   ClusterCount) <= TotalClusters );

    hDC = memoryDC.GetMemoryDC();
    Width = memoryDC.GetWidth();
    Height = memoryDC.GetHeight();
    MaxLength = memoryDC.GetMaxLength();

    // Calculate some stuff

    float Scale = static_cast<float>(MaxLength) / TotalClusters;
    // TODO: Test this drawing code with scale above 1.0.
    //       The current code should work in theory (to scale up) but if it doesn't the proposed fix-code below, commented:
    //if ( Scale > 1.0 )
    //  Scale = 1.0;
    LONG Length = static_cast<LONG>( ceil( static_cast<float>(ClusterCount) * Scale ) );

    if ( Length <= 0 )
        return;

    //LONG ScaleA = (ClusterStart  * Scale / TotalClusters);
    //LONG ScaleB = (ClusterEnd * Scale / TotalClusters);

    //x1 = (double)(ClusterStart * Scale) / (double)Width;
    //x1 = (double)((double)ClusterStart / (double)Width) * Scale;
    //y1 = (ClusterStart * Scale) % Width;
    //y1 = fmod( (ClusterStart * Scale), Width );
    //y1 = fmod( (double)ClusterStart / (double)Width), Scale );
    //x2 = ScaleB % Width;
    //y2 = ScaleB / Width;
    x1 = static_cast<LONGLONG>( fmod( static_cast<float>(ClusterStart * Scale), static_cast<float>(Width) ) );
    y1 = static_cast<LONGLONG>( static_cast<float>(ClusterStart * Scale) / static_cast<float>(Width) );
    
    // Calculation done, now check if there is any point in drawing it
    //if ( x1 <= 0 amp;amp; x2 <= 0 )
    //  return;

    // Save original object.
    HGDIOBJ oldPen = SelectObject( hDC, GetStockObject(DC_PEN) );
    // Change the DC pen color
    SetDCPenColor( hDC, ClusterMapColors[ color ] );
    //if ( InUse == 1 )
    //  SetDCPenColor( hDC, RGB(160, 160, 160) ); // 160 == 0xA0
    //else if ( InUse == 0 )
    //  SetDCPenColor( hDC, RGB(0xFF, 0xFF, 0xFF) );
    //  //SetDCPenColor( hDC, RGB(0x00, 0x00, 0x00) );
    //else if ( InUse == 2 )
    //  //SetDCPenColor( hDC, RGB(0x00, 0xFF, 0x00) );
    //  SetDCPenColor( hDC, RGB(0x00, 0x00, 0xFF) ); // Blue
    //else if ( InUse == 3 )
    //  //SetDCPenColor( hDC, RGB(0x00, 160, 0xFF) );
    //  SetDCPenColor( hDC, RGB(0x00, 0xFF, 0x00) ); // Green
    //else if ( InUse == 4 )
    //  //SetDCPenColor( hDC, RGB(0x00, 160, 0xFF) );
    //  SetDCPenColor( hDC, RGB(0xFF, 0x00, 0x00) ); // Red
    //else if ( InUse == 5 ) // Debug
    //  SetDCPenColor( hDC, RGB(0xFF, 0xFF, 0x00) ); // Yellow

    //----------------------------------------------------
    // Only Draw code in here

    LONG step;
    LONG line = static_cast<LONG>( y1 );
    while ( Length > 0 )
    {
        step = Min( Length, static_cast<LONG>(Width - x1) );
        //step = Min( Length, Width );

        if ( MoveToEx( hDC, static_cast<int>(x1), static_cast<int>(line), NULL ) )
            LineTo( hDC, static_cast<int>(step   x1), static_cast<int>(line) );

        Length -= step;
        x1 = 0;
        line  ;
    }

#if 0
    Width = clientRect.right;
    Height = clientRect.bottom;

    int Scale = (Width * Height) / TotalClusters;

    //x2 = (ClusterEnd * Scale ) % Width;
    //y2 = (ClusterEnd * Scale ) / Width;

    // Start
    int startX = (ClusterStart * Width * Height / TotalClusters ) % Width;
    int startY = (ClusterStart * Width * Height / TotalClusters ) / Width;

    int endX = ;
    int endY;
    LONG Length = ClusterEnd - ClusterStart;

    while ( Length > 0 )
    {
        LONG len = Min( Length, Width );
        len -= startX;
        
        MoveToEx( hDC, startX, startY, NULL );
        LineTo( hDC, startX   len, endY );

        Length -= len;
        startY  ; // Next line
        startX = 0;
    }
#endif

    //----------------------------------------------------

    // Restore original object.
    SelectObject( hDC, oldPen );
}
 

Комментарии:

1. » Я не могу понять, как это предотвратить». -> Не используйте FP math. Почтовый индекс используется для лучших ответов.

2. Как вы можете не использовать FP math при масштабировании? Я нашел резервную копию кода чертежа.

Ответ №1:

… из-за математики с плавающей запятой последний пиксель боролся с первым пикселем нового кластера, когда я уменьшил его, чтобы он соответствовал окну

Вместо математики с плавающей запятой низкой точности используйте математику целых чисел. Проще точно контролировать крайние случаи.

Сформируйте масштаб в виде пары числитель, знаменатель:

 // float Scale = static_cast<float>(MaxLength) / TotalClusters;
long ScaleNumerator = MaxLength;
long ScaleDenominator = TotalClusters;
 

Замените FP целочисленным кодом. При необходимости используйте более широкую целочисленную математику. Пример:

 // x1 = static_cast<LONGLONG>( fmod( static_cast<float>(ClusterStart * Scale), 
//     static_cast<float>(Width) ) );
long long m = 1LL * ClusterStart * ScaleNumerator / ScaleDenominator;
x1 =  m % Width;
y1 =  m / Width;
 

Возможно, в комментируемом коде OP возникли проблемы с переполнением. Альтернатива:

 // int startX = (ClusterStart * Width * Height / TotalClusters ) % Width;
int startX = (1LL * ClusterStart * Width * Height / TotalClusters ) % Width; 
 

Если это все еще приводит к проблемам с крайним случаем, корректировку целочисленного математического кода легче исправить, чем FP-код.