Digital Image Processing Assignment II

Main Contents

  • Image binarization;
  • Binary image erosion;
  • Binary image dilation;
  • Binary image opening;
  • Binary image closing.

Step One: Define Some Functions

Here we define a function that generate a blank grayscale image from a given image,

void gen_gray(BITMAP *bmImg, BITMAP *bmGray) {
    BITMAPINFOHEADER *bmiHeader = &bmImg->bmInfo->bmiHeader;
    bmGray->bmHeader = bmImg->bmHeader;
    bmGray->bmInfoSize = sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD) << 8);
    bmGray->bmInfo = (BITMAPINFO *) malloc(bmGray->bmInfoSize);
    bmGray->bmInfo->bmiHeader = *bmiHeader;
    bmGray->bmInfo->bmiHeader.biBitCount = 8;
    for (int16_t i = 0; i < 256; ++i) {
        RGBQUAD *rgb = &bmGray->bmInfo->bmiColors[i];
        rgb->rgbBlue = i;
        rgb->rgbGreen = i;
        rgb->rgbRed = i;
        rgb->rgbReserved = 0;
    }
    init_bmp(bmGray);
    bmGray->bmHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + bmGray->bmInfoSize;
    bmGray->bmHeader.bfSize = bmGray->bmHeader.bfOffBits + (bmGray->bmBytesPerRow * bmiHeader->biHeight);
}

and a function that change a color image into grayscale.

void color2gray(BITMAP *bmImg, BITMAP *bmGray) {
    BITMAPINFOHEADER *bmiHeader = &bmImg->bmInfo->bmiHeader;
    gen_gray(bmImg, bmGray);
    uint8_t minY = 255, maxY = 0;
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg->bmBytesPerRow + w * bmImg->bmBytesPerPel;
            uint8_t *B = &bmImg->bmData[pos];
            uint8_t *G = &bmImg->bmData[pos + 1];
            uint8_t *R = &bmImg->bmData[pos + 2];
            uint8_t Y = adjust(0.299 * *R + 0.587 * *G + 0.114 * *B);
            if (Y < minY) {
                minY = Y;
            }
            if (Y > maxY) {
                maxY = Y;
            }
        }
    }
    // rearrange gray indensity
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg->bmBytesPerRow + w * bmImg->bmBytesPerPel;
            uint8_t *B = &bmImg->bmData[pos];
            uint8_t *G = &bmImg->bmData[pos + 1];
            uint8_t *R = &bmImg->bmData[pos + 2];
            uint8_t Y = adjust(0.299 * *R + 0.587 * *G + 0.114 * *B);
            uint32_t _pos = h * bmGray->bmBytesPerRow + w * bmGray->bmBytesPerPel;
            bmGray->bmData[_pos] = adjust(255. * (Y - minY) / (maxY - minY));
        }
    }
}

Step Two: Change Gray to Binary

In order to binarize the image, we need determine a threshold, and change pixels whose grayscale is below the threshold to black, and the others to white. But how to find the optimal threshold? There’s an excellent algorithm called Otsu’s method, which, in brief, maximize inter-class variance.

void gray2binary(BITMAP *bmGray, BITMAP *bmBinary) {
    BITMAPINFOHEADER *bmiHeader = &bmGray->bmInfo->bmiHeader;
    gen_gray(bmGray, bmBinary);
    double *cnt = (double *) malloc(1 << 16);
    memset(cnt, 0, 1 << 16);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmGray->bmBytesPerRow + w * bmGray->bmBytesPerPel;
            ++cnt[bmGray->bmData[pos]];
        }
    }
    double omegaK = 0, muK = 0, muT = 0, maxB = 0;
    uint8_t threshold = 0;
    for (int16_t k = 0; k < 256; ++k) {
        cnt[k] /= bmiHeader->biHeight * bmiHeader->biWidth;
        muT += k * cnt[k];
    }
    for (int16_t k = 0; k < 256; ++k) {
        omegaK += cnt[k];
        muK += k * cnt[k];
        double sigmaK = (muT * omegaK - muK) * (muT * omegaK - muK) / omegaK / (1 - omegaK);
        if (maxB < sigmaK) {
            maxB = sigmaK;
            threshold = k;
        }
    }
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmGray->bmBytesPerRow + w * bmGray->bmBytesPerPel;
            bmBinary->bmData[pos] = bmGray->bmData[pos] < threshold ? 0 : 255;
        }
    }
}

Step Three: Erosion and Dilation

Let’s say that white is the foreground and black is the background. Assume you have a solid circle, and you can only place it on white pixels, which means the circle shouldn’t cover the black pixels. We make the available area where the center can be put white and the others black, and get a new image—the result of erosion.

void erode(BITMAP *bmBinary, BITMAP *bmErosion, uint32_t border) {
    BITMAPINFOHEADER *bmiHeader = &bmBinary->bmInfo->bmiHeader;
    gen_gray(bmBinary, bmErosion);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmBinary->bmBytesPerRow + w * bmBinary->bmBytesPerPel; 
            uint8_t flag = 255;
            for (uint32_t _h = h - border; _h <= h + border; ++_h) {
                if (_h >= 0 && _h < bmiHeader->biHeight) {
                    uint32_t delta = border - abs(h - _h);
                    for (uint32_t _w = w - delta; _w <= w + delta; ++_w) {
                        if (_w >= 0 && _w < bmiHeader->biWidth) {
                            uint32_t _pos = _h * bmBinary->bmBytesPerRow + _w * bmBinary->bmBytesPerPel;
                            flag &= bmBinary->bmData[_pos];
                        }
                    }
                }
            }
            bmErosion->bmData[pos] = flag;
        }
    }
}

This time, you want to put the center of the circle on white pixels regardless of whether other points lie on black pixels. We make the area that can be covered by the circle white and the others black. This is what dilation do. Surprisingly, the code is very very similar with erosion.

void dilate(BITMAP *bmBinary, BITMAP *bmDilation, uint32_t border) {
    BITMAPINFOHEADER *bmiHeader = &bmBinary->bmInfo->bmiHeader;
    gen_gray(bmBinary, bmDilation);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmBinary->bmBytesPerRow + w * bmBinary->bmBytesPerPel; 
            uint8_t flag = 0;
            for (uint32_t _h = h - border; _h <= h + border; ++_h) {
                if (_h >= 0 && _h < bmiHeader->biHeight) {
                    uint32_t delta = border - abs(h - _h);
                    for (uint32_t _w = w - delta; _w <= w + delta; ++_w) {
                        if (_w >= 0 && _w < bmiHeader->biWidth) {
                            uint32_t _pos = _h * bmBinary->bmBytesPerRow + _w * bmBinary->bmBytesPerPel;
                            flag |= bmBinary->bmData[_pos];
                        }
                    }
                }
            }
            bmDilation->bmData[pos] = flag;
        }
    }
}

Step Four: Opening and Closing

In fact, opening operation and closing operation are just the combination of the two operations we mentioned above, the only difference is the order. Opening operation is an erosion followed by a dilation, while closing operation is the reverse.

Visually speaking, opening operation makes the gaps between black pixels disappeared, and closing operation makes the gaps between white pixels disappeared.

Note that these two operations are both idempotent, which means performing one of them multiple times is equivalent to performing it one time.

Give Up Sometimes

The expression “Never, never give up” means to keep trying and never stop working for your goals. Do you agree or disagree with this statement? Use specific reasons and examples to support your answer.

Topic

Many people believe in the saying that we should never give up casually, which tells them to work incessantly for their goals. Admittedly, this spirit-lifting saying has given countless people a pat on the back, inspired them to strive for their long-held dreams and acquire prosperity in the end. But generally speaking, I suppose we don’t have to shy away from giving up. Sometimes we need to put aside our persistence and accept the abnegation.

First of all, the initial orientation matters. We can’t guarantee that the first step we take is towards the right direction, which means we can be heading the wrong without perceiving it. If so, it would be better to just turn around than to press on. There’s no need to sacrifice your success for simply senseless insistence. One example of this is an ancient idiom from China, narrating a man who intended to go south steered his carriage to the north regardless of the prompting from a passer-by. He had not realized the sheer meaninglessness of his action. Of course, he has not arrived his destination yet. Had he listened to others’ advice and turned back, he would have reached it.

Besides, we should pay heed to the progress. Not only are we likely to move forward to where we do not expect at the beginning, but also during the process, and thus adjusting ourselves dynamically should be attached significance to. In addition, even though we know clearly what we are doing and why we are doing it, we may lack the capacity to do it well, and the progress can fall short of our expectation. When we’re patently unsuitable for tackling certain type of things, insisting may not be the optimal option. Some classmates in my high school will be rather representative instances. As soon as they entered the school, each of them picked an Olympiad in a certain subject. Yet before long, they found themselves not very good at competition, thereby opting out, focusing on regular curriculum. It turned out ultimately that they obtained fairly high scores in the college entrance examination. We can derive from it the fact that giving up does not necessarily mean losing, but somehow means wining.

What’s more, the outcome may be perplexing. When it come out, it’s time to judge whether it is right or not. We can scarcely ensure that the result will always fulfil our anticipation, so when we receive an unexpected result which is against the basic facts, probably it should not be calculated in statistics. At this time, alleging that the result has no problem seems a bit stubborn. On the other hand, if the result does not correspond with what we used to deem it should be, the chances are what deceives you is not the result, but the acknowledgement. If we look back upon history of science and technology, there are innumerable examples, such as Galileo who doubted the previous law of free fall, Bohr who suspected the previous nuclear model, etc. All of these indicate that even the most seeming truth has the possibility to be broken someday. The interesting thing is that, the result and the truth were dropped alternately, which set the pace for the development of human civilization. We wouldn’t have today’s advancement if there were no giving up in the world.

The old saying “Never, never give up” will continue to exert its positive attitude and lift people up. However, I take the viewpoint that people sometimes need to give up what they think, have or believe. This is not persuading you to give up everything in your life, but encouraging you to give up in a more sensible way, in which you will better encounter what you love, accomplish what you dream, and make what you desire to be.

Digital Image Processing Assignment I

Main Contents

  • Read a color bmp;
  • RGB->YUV;
  • Color to gray: gray=Y in YUV color space;
  • Rearrange gray intensity to lie between [0,255];
  • Write a grayscale bmp;
  • Change the luminance value Y;
  • YUV->RGB;
  • Write a color bmp.

Step One: BMP File Structure

A common BMP file is comprised of four part: image file header, image information header, palette and image data.

The image file header is a struct whose length is 14 bytes. Here gives its definition,

typedef struct tagBITMAPFILEHEADER {
    WORD bfType;  
    DWORD bfSize;  
    WORD bfReserved1;  
    WORD bfReserved2; 
    DWORD bfOffBits; 
} BITMAPFILEHEADER;

and the explanation for every variable.

  • bfType: must always be set to ‘BM’ to declare that this is a .bmp-file;
  • bfSize: specifies the size of the file in bytes;
  • bfReserved1: must always be set to zero;
  • bfReserved2: must always be set to zero;
  • bfOffBits: specifies the offset from the beginning of the file to the bitmap data.

The image information header is also a struct, while its length is 40 bytes. The definition

typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;     
    LONG biWidth;    
    LONG biHeight;    
    WORD biPlanes;    
    WORD biBitCount   
    DWORD biCompression;  
    DWORD biSizeImage;   
    LONG biXPelsPerMeter;  
    LONG biYPelsPerMeter;  
    DWORD biClrUsed;  
    DWORD biClrImportant; 
} BITMAPINFOHEADER;

The explanation

  • biSize: number of bytes to define BITMAPINFORHEADER structure;
  • biWidth: image width (number of pixels);
  • biHeight: image height (number of pixels), note that if it’s a positive number, the image is inverted, otherwise upright;
  • biPlanes: number of planes, should always be 1;
  • biBitCount: bits per pixel, which may be 1, 4, 8, 16, 24, 32;
  • biCompression: compression type, only non-compression(BI_RGB) is discussed here;
  • biSizeImage: image size with bytes, when biCompression is BI_RGB, biSizeImage is 0;
  • biXPelsPerMeter: horizontal resolution, pixels per meter;
  • biYPelsPerMeter: vertical resolution, pixels per meter;
  • biClrUsed: number of color indices used in the bitmap, when it’s 0, all the palette items are used;
  • biClrImportant: number of important color indices for image display, when it’s 0, all items are important.

The palette has a series of RGBQUADs, which is defined like this.

typedef struct tagRGBQUAD {
    uint8_t rgbBlue;
    uint8_t rgbGreen;
    uint8_t rgbRed;
    uint8_t rgbReserved;
} RGBQUAD;

Note that the order of the color is blue, green, and red, not the reverse. The number of RGBQUADs is decided by biBitCount and biClrUsed.

Next we need to define BITMAPINFO.

typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
} BITMAPINFO;

As we know nothing about the number of RGBQUADs an image uses, the BITMAPINFO should be defined as a pointer so that right amount of memory can be allocated to it.

The image data contains color of all pixels, and every biBitCount bit(s) represents a pixel.

At last, we define a struct to storage a full BMP image.

typedef struct tagBITMAP {
    BITMAPFILEHEADER bmHeader;
    BITMAPINFO *bmInfo;
    uint32_t bmInfoSize;
    uint32_t bmBytesPerRow;
    uint8_t bmBytesPerPel;
    uint8_t *bmData;
} BITMAP;

When defining the two structs, we need to add a line #pragma pack(push, 1) to avoid struct padding.

Step Two: Read/Write a BMP File

A BMP file is a binary file, so we need to add "b" to the second parameter when using fopen.

Another important thing is that the number of bytes in one row must always be adjusted to fit into the border of a multiple of four, and we need to calculate how many bytes are there in one row.

For convenience, we define an initialize function, which receives bmHeader and bmInfo, and initializes other variables.

// given bmHeader and bmInfo, initialize others
void init_bmp(BITMAP *bmImg) {
    BITMAPINFOHEADER *bmiHeader = &(bmImg->bmInfo->bmiHeader);
    bmImg->bmBytesPerRow = ((bmiHeader->biWidth * bmiHeader->biBitCount + 31) >> 5) << 2;
    bmImg->bmBytesPerPel = bmiHeader->biBitCount >> 3;
    bmImg->bmData = (uint8_t *) malloc(bmImg->bmBytesPerRow * bmiHeader->biHeight);
}

The read function is showed below.

// read a BMP from file
void read_bmp(BITMAP *bmImg, char *filepath) {
    FILE *fiInImg = fopen(filepath, "rb");
    BITMAPINFOHEADER bmiHeader;
    fread(&(bmImg->bmHeader), sizeof(BITMAPFILEHEADER), 1, fiInImg);
    fread(&bmiHeader, sizeof(BITMAPINFOHEADER), 1, fiInImg);
    // if biBitCount is less than 16, use all the palette, otherwise do not use palette
    if (bmiHeader.biBitCount < 16) {
        bmImg->bmInfoSize = sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD) << bmiHeader.biBitCount);
    } else {
        bmImg->bmInfoSize = sizeof(BITMAPINFOHEADER);
    }
    bmImg->bmInfo = (BITMAPINFO *) malloc(bmImg->bmInfoSize);
    bmImg->bmInfo->bmiHeader = bmiHeader;
    if (bmiHeader.biBitCount < 16) {
        fread(bmImg->bmInfo->bmiColors, sizeof(RGBQUAD), 1 << bmiHeader.biBitCount, fiInImg);
    }
    init_bmp(bmImg);
    fread(bmImg->bmData, bmImg->bmBytesPerRow, bmiHeader.biHeight, fiInImg);
    fclose(fiInImg);
}

The write function looks similar with the read function.

// write a BMP to file
void write_bmp(BITMAP *bmImg, char *filepath) {
    FILE *fiOutImg = fopen(filepath, "wb");
    fwrite(&(bmImg->bmHeader), sizeof(BITMAPFILEHEADER), 1, fiOutImg);
    fwrite(bmImg->bmInfo, bmImg->bmInfoSize, 1, fiOutImg);
    fwrite(bmImg->bmData, bmImg->bmBytesPerRow, bmImg->bmInfo->bmiHeader.biHeight, fiOutImg);
    fclose(fiOutImg);
}

Step Three: Change Color to Gray

To make things easier, we define a function to duplicate a BMP file,

// duplicate a BMP
void copy_bmp(BITMAP *bmDes, BITMAP *bmSrc) {
    memcpy(bmDes, bmSrc, sizeof(BITMAP));
    bmDes->bmInfo = (BITMAPINFO *) malloc(bmSrc->bmInfoSize);
    memcpy(bmDes->bmInfo, bmSrc->bmInfo, bmSrc->bmInfoSize);
    BITMAPINFOHEADER *bmiHeader = &(bmSrc->bmInfo->bmiHeader);
    bmDes->bmData = (uint8_t *) malloc(bmSrc->bmBytesPerRow * bmiHeader->biHeight);
    memcpy(bmDes->bmData, bmSrc->bmData, bmSrc->bmBytesPerRow * bmiHeader->biHeight);
}

and a function to make sure the RGB value of a pixel lie between [0, 255].

// make RGB value legal
uint8_t adjust(double val) {
    int16_t ret = (int16_t) (val + 0.5);
    return ret < 0 ? 0 : ret > 255 ? 255 : ret;
}

We need to change RGB to YUV first, as the grayscale is determined by Y value. The formula is

$$\begin{bmatrix} 0.299 & 0.587 & 0.114 \\ -0.147 & -0.289 & 0.436 \\ 0.615 & -0.515 & -0.100 \end{bmatrix} \times \begin{bmatrix} R \\ G \\ B \end{bmatrix} = \begin{bmatrix} Y \\ U \\ V \end{bmatrix}$$

    // calculate YUV value
    double *bmYUV = (double *) malloc(sizeof(double) * bmImg.bmBytesPerRow * bmiHeader->biHeight);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            uint8_t *B = &bmImg.bmData[pos];
            uint8_t *G = &bmImg.bmData[pos + 1];
            uint8_t *R = &bmImg.bmData[pos + 2];
            bmYUV[pos] = 0.299 * *R + 0.587 * *G + 0.114 * *B;
            bmYUV[pos + 1] = -0.147 * *R - 0.289 * *G + 0.436 * *B;
            bmYUV[pos + 2] = 0.615 * *R - 0.515 * *G - 0.100 * *B;
        }
    }

To change color to gray, we can simply make the R, G and B value of a pixel equal to its Y value. It would be more complex if we want to change it into an image with biBitCount equal to 8, because we need to set the palette manually.

One more step, rearrange gray intensity to lie between [0,255]. It’s just a math problem. So the code

    // color to gray
    BITMAP bmGray;
    bmGray.bmHeader = bmImg.bmHeader;
    bmGray.bmInfoSize = sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD) << 8);
    bmGray.bmInfo = (BITMAPINFO *) malloc(bmGray.bmInfoSize);
    bmGray.bmInfo->bmiHeader = *bmiHeader;
    bmGray.bmInfo->bmiHeader.biBitCount = 8;
    for (int i = 0; i < 256; ++i) {
        RGBQUAD *rgb = &(bmGray.bmInfo->bmiColors[i]);
        rgb->rgbBlue = i;
        rgb->rgbGreen = i;
        rgb->rgbRed = i;
        rgb->rgbReserved = 0;
    }
    init_bmp(&bmGray);
    uint8_t min = 255, max = 0;
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            if (*Y < min) {
                min = *Y;
            }
            if (*Y > max) {
                max = *Y;
            }
        }
    }
    // rearrange gray indensity
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            uint32_t _pos = h * bmGray.bmBytesPerRow + w * bmGray.bmBytesPerPel;
            bmGray.bmData[_pos] = adjust(255 * (*Y - min) / (max - min));
        }
    }

Step Four: Change the Luminance

The luminance is depend on Y value, too. What we need to do is just changing the Y value and applying the inverse formula below.

$$\begin{bmatrix} 1.000 & 0.000 & 1.140 \\ 1.000 & -0.3946 & -0.5805 \\ 1.000 & 2.032 & -0.0005 \end{bmatrix} \times \begin{bmatrix} Y \\ U \\ V \end{bmatrix} = \begin{bmatrix} R \\ G \\ B \end{bmatrix}$$

And the code is simple.

    // change luminance
    BITMAP bmLight, bmDark;
    copy_bmp(&bmLight, &bmImg);
    copy_bmp(&bmDark, &bmImg);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            double *U = &bmYUV[pos + 1];
            double *V = &bmYUV[pos + 2];
            bmLight.bmData[pos] = adjust(*Y + 25 + 2.032 * *U - 0.0005 * *V);
            bmLight.bmData[pos + 1] = adjust(*Y + 25 - 0.3946 * *U - 0.5805 * *V);
            bmLight.bmData[pos + 2] = adjust(*Y + 25 + 1.140 * *V);
            bmDark.bmData[pos] = adjust(*Y - 50 + 2.032 * *U - 0.0005 * *V);
            bmDark.bmData[pos + 1] = adjust(*Y - 50 - 0.3946 * *U - 0.5805 * *V);
            bmDark.bmData[pos + 2] = adjust(*Y - 50 + 1.140 * *V);
        }
    }

Step Five: Complete Source Code

bmp.h

#ifndef _BMP_H_
#define _BMP_H_

#include <stdint.h>

#pragma pack(push, 1) // avoid struct padding

typedef struct tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    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;
} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {
    uint8_t rgbBlue;
    uint8_t rgbGreen;
    uint8_t rgbRed;
    uint8_t rgbReserved;
} RGBQUAD;

typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
} BITMAPINFO;

typedef struct tagBITMAP {
    BITMAPFILEHEADER bmHeader;
    BITMAPINFO *bmInfo;
    uint32_t bmInfoSize;
    uint32_t bmBytesPerRow;
    uint8_t bmBytesPerPel;
    uint8_t *bmData;
} BITMAP;

#pragma pack(pop)

#endif

bmp.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bmp.h"

// given bmHeader and bmInfo, initialize others
void init_bmp(BITMAP *bmImg) {
    BITMAPINFOHEADER *bmiHeader = &(bmImg->bmInfo->bmiHeader);
    bmImg->bmBytesPerRow = ((bmiHeader->biWidth * bmiHeader->biBitCount + 31) >> 5) << 2;
    bmImg->bmBytesPerPel = bmiHeader->biBitCount >> 3;
    bmImg->bmData = (uint8_t *) malloc(bmImg->bmBytesPerRow * bmiHeader->biHeight);
}

// read a BMP from file
void read_bmp(BITMAP *bmImg, char *filepath) {
    FILE *fiInImg = fopen(filepath, "rb");
    BITMAPINFOHEADER bmiHeader;
    fread(&(bmImg->bmHeader), sizeof(BITMAPFILEHEADER), 1, fiInImg);
    fread(&bmiHeader, sizeof(BITMAPINFOHEADER), 1, fiInImg);
    // if biBitCount is less than 16, use all the palette, otherwise do not use palette
    if (bmiHeader.biBitCount < 16) {
        bmImg->bmInfoSize = sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD) << bmiHeader.biBitCount);
    } else {
        bmImg->bmInfoSize = sizeof(BITMAPINFOHEADER);
    }
    bmImg->bmInfo = (BITMAPINFO *) malloc(bmImg->bmInfoSize);
    bmImg->bmInfo->bmiHeader = bmiHeader;
    if (bmiHeader.biBitCount < 16) {
        fread(bmImg->bmInfo->bmiColors, sizeof(RGBQUAD), 1 << bmiHeader.biBitCount, fiInImg);
    }
    init_bmp(bmImg);
    fread(bmImg->bmData, bmImg->bmBytesPerRow, bmiHeader.biHeight, fiInImg);
    fclose(fiInImg);
}

// duplicate a BMP
void copy_bmp(BITMAP *bmDes, BITMAP *bmSrc) {
    memcpy(bmDes, bmSrc, sizeof(BITMAP));
    bmDes->bmInfo = (BITMAPINFO *) malloc(bmSrc->bmInfoSize);
    memcpy(bmDes->bmInfo, bmSrc->bmInfo, bmSrc->bmInfoSize);
    BITMAPINFOHEADER *bmiHeader = &(bmSrc->bmInfo->bmiHeader);
    bmDes->bmData = (uint8_t *) malloc(bmSrc->bmBytesPerRow * bmiHeader->biHeight);
    memcpy(bmDes->bmData, bmSrc->bmData, bmSrc->bmBytesPerRow * bmiHeader->biHeight);
}

// write a BMP to file
void write_bmp(BITMAP *bmImg, char *filepath) {
    FILE *fiOutImg = fopen(filepath, "wb");
    fwrite(&(bmImg->bmHeader), sizeof(BITMAPFILEHEADER), 1, fiOutImg);
    fwrite(bmImg->bmInfo, bmImg->bmInfoSize, 1, fiOutImg);
    fwrite(bmImg->bmData, bmImg->bmBytesPerRow, bmImg->bmInfo->bmiHeader.biHeight, fiOutImg);
    fclose(fiOutImg);
}

// make RGB value legal
uint8_t adjust(double val) {
    int16_t ret = (int16_t) (val + 0.5);
    return ret < 0 ? 0 : ret > 255 ? 255 : ret;
}

int main(int argc, char *argv[]) {
    // read bmp file
    BITMAP bmImg;
    read_bmp(&bmImg, "original.bmp");
    BITMAPINFOHEADER *bmiHeader = &(bmImg.bmInfo->bmiHeader);

    // calculate YUV value
    double *bmYUV = (double *) malloc(sizeof(double) * bmImg.bmBytesPerRow * bmiHeader->biHeight);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            uint8_t *B = &bmImg.bmData[pos];
            uint8_t *G = &bmImg.bmData[pos + 1];
            uint8_t *R = &bmImg.bmData[pos + 2];
            bmYUV[pos] = 0.299 * *R + 0.587 * *G + 0.114 * *B;
            bmYUV[pos + 1] = -0.147 * *R - 0.289 * *G + 0.436 * *B;
            bmYUV[pos + 2] = 0.615 * *R - 0.515 * *G - 0.100 * *B;
        }
    }

    // color to gray
    BITMAP bmGray;
    bmGray.bmHeader = bmImg.bmHeader;
    bmGray.bmInfoSize = sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD) << 8);
    bmGray.bmInfo = (BITMAPINFO *) malloc(bmGray.bmInfoSize);
    bmGray.bmInfo->bmiHeader = *bmiHeader;
    bmGray.bmInfo->bmiHeader.biBitCount = 8;
    for (int i = 0; i < 256; ++i) {
        RGBQUAD *rgb = &(bmGray.bmInfo->bmiColors[i]);
        // to prove that the palette works well, play a small trick
        rgb->rgbBlue = (i >> 4) << 4;
        rgb->rgbGreen = (i >> 4) << 4;
        rgb->rgbRed = (i >> 4) << 4;
        rgb->rgbReserved = 0;
    }
    init_bmp(&bmGray);
    uint8_t min = 255, max = 0;
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            if (*Y < min) {
                min = *Y;
            }
            if (*Y > max) {
                max = *Y;
            }
        }
    }
    // rearrange gray indensity
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            uint32_t _pos = h * bmGray.bmBytesPerRow + w * bmGray.bmBytesPerPel;
            bmGray.bmData[_pos] = adjust(255 * (*Y - min) / (max - min));
        }
    }
    write_bmp(&bmGray, "gray.bmp");

    // change luminance
    BITMAP bmLight, bmDark;
    copy_bmp(&bmLight, &bmImg);
    copy_bmp(&bmDark, &bmImg);
    for (uint32_t h = 0; h < bmiHeader->biHeight; ++h) {
        for (uint32_t w = 0; w < bmiHeader->biWidth; ++w) {
            uint32_t pos = h * bmImg.bmBytesPerRow + w * bmImg.bmBytesPerPel;
            double *Y = &bmYUV[pos];
            double *U = &bmYUV[pos + 1];
            double *V = &bmYUV[pos + 2];
            bmLight.bmData[pos] = adjust(*Y + 25 + 2.032 * *U - 0.0005 * *V);
            bmLight.bmData[pos + 1] = adjust(*Y + 25 - 0.3946 * *U - 0.5805 * *V);
            bmLight.bmData[pos + 2] = adjust(*Y + 25 + 1.140 * *V);
            bmDark.bmData[pos] = adjust(*Y - 50 + 2.032 * *U - 0.0005 * *V);
            bmDark.bmData[pos + 1] = adjust(*Y - 50 - 0.3946 * *U - 0.5805 * *V);
            bmDark.bmData[pos + 2] = adjust(*Y - 50 + 1.140 * *V);
        }
    }
    write_bmp(&bmLight, "light.bmp");
    write_bmp(&bmDark, "dark.bmp");

    return 0;
}

Primes

题目描述

This is an interactive problem.
For two positive integers $x, y$, we define $\pi(x, y)$ to be the number of distinct primes that divide both $x$ and $y$. For example $\pi(2, 3) = 0, \; \pi(8, 16) = 1$ and $\pi(30, 105) = 2$.
For two positive integers $a, b$, where $a \le b$, we define $S(a, b)$ to be the sum of values $\pi(x, y)$ over all pairs of integers $(x, y)$ satisfying $a \le x \lt y \le b$.
Your task is to compute the values $S(a, b)$ for many query pairs $(a, b)$. To make your task more challenging, all the queries have to be answered online.

题意概述

给定两个整数$a, b$,定义$\pi(x, y)$为所有整除$x$和$y$的质数的个数,$S(a, b)$为所有满足$a \le x \lt y \le b$的$\pi(x, y)$的和,求$S(a, b)$。有$q$组询问,强制在线。

数据范围:$1 \le q \le 5 \times 10^4, \; 1 \le a \le b \le 10^6$。

算法分析

分别考虑每个质数对答案的贡献。若在区间$[a, b]$中有$k$个数能被质数$p$整除,则$p$对答案的贡献为${k \choose 2}$。

首先筛出所有质数。但是每次询问直接枚举质数会超时。考虑对于一个整数$n$,$\lfloor {n \over i} \rfloor$只有$O(\sqrt{n})$种不同的取值。因此可以对$\lfloor {a \over i} \rfloor$和$\lfloor {b \over i} \rfloor$进行分段枚举。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

int const N = 1000005;

int tp, prime[N], rec[N], vis[N], sum[N];

void init() {
    for (int i = 2; i < N; ++i) {
        if (!vis[i]) {
            prime[tp++] = i;
        }
        for (int j = 0; j < tp && i * prime[j] < N; ++j) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                break;
            }
        }
    }
    for (int i = 2; i < N; ++i)
        sum[i] = sum[i - 1] + !vis[i];
}

int main() {
    init();
    int q;
    scanf("%d", &q);
    for (; q--;) {
        int a, b;
        scanf("%d%d", &a, &b);
        long long ans = 0;
        for (int i = 0; i < tp && prime[i] < b;) {
            int cnt = b / prime[i] - (a - 1) / prime[i];
            int nxt = 1e9;
            if (b / prime[i]) nxt = std::min(nxt, b / (b / prime[i]));
            if ((a - 1) / prime[i]) nxt = std::min(nxt, (a - 1) / ((a - 1) / prime[i]));
            ans += 1ll * cnt * (cnt - 1) / 2 * (sum[nxt] - i);
            i = sum[nxt];
        }
        printf("%lld\n", ans);
        fflush(stdout);
    }
    return 0;
}

Spring II

It was a surprise to discover a new nest opposite the window.

Through the telescope, I scrupulously watched the doves in the nest, who were reposing restfully. Narrowly I discerned that they were a family comprised of a mother and two babies. Sighing with relief, I thought, “Finally the dove has returned, and luckily I can still see the family.” I even expected to witness the first flight of the baby doves.

I started wondering where the doves came from, as it was odd that they were inclined to construct their home here between the buildings, and after a period of cogitating, I ultimately deduced that once they might live in the decrepit houses nearby which were to be pulled down owing to the modernization and urbanization.

From this viewpoint, the doves seemed to be pitiable. “It can be tough for them to find a congenial shelter in the city.” I uttered in my heart, as I observed them fluttering their gray wings. It looked as if they were enjoying the serene life. Yet my brain did not cease contemplating.

Living in the era with rapid technological advancements which largely hasten our pace of life, we ineluctably forgo the joyous time being close to nature. And not until the demise of the erstwhile happiness we attained from nature comes do we realize that it is deplorable to turn ourselves into a hardened machine.

The blood-red sun was sinking and a livid cloud in the east received its rays. A twinge of woe surged through me. If we humanity kept pursuing interests by damaging the environment, what would the attendant consequence be? Would it only be driving the doves homeless?

I couldn’t help recalling the days when the city was shrouded in thick smog, and people could scarcely distinguish the things in the distance. And the rivers in the city, which were limpid before, had become turbid in recent decades. The foregoing instances were attributed to human activities. Not to mention the global warming, the ozone depletion, and the extinction of multitudinous species. Urbanization was just all of these in miniature.

The sky was tinted with black inch by inch, and the doves quieted down again. I shifted my eyes from them.

I leaned on the windowsill, looking up at the firmament, which used to be spangled with innumerable stars that were now unseen to us. Embedded aloft in the inky night sky, the pale yellow full moon cast its glow over the city, like an infinite piece of cloth mantling, yet faraway as the neon lights were, their harsh beams penetrated the textile that the moon carefully sewed without any difficulty, as if it was nonexistent. The dazzling rays pricked not only my eyes, but also my heart. “Perhaps the doves dread the blinding light, too.”

There was a time when all the doves could flit from tree to tree jovially in the dense and emerald woodlands, where they interacted with other creatures complying with the principles of nature. It was not until human beings arose that changes occurred. Trees were felled at first, and then sewage and fumes were disgorged by pipes. As the time elapsed our compunction was attenuated unawares by the profits we gained at the price of the environment we lived. We averted our gaze from what was prime, focusing on what was subordinate. The cupidity for gain and the scant heed we paid to conservation incurred the mire we confront today.

We really should pause, ruminate for a while, and inquire ourselves, what is our original orientation, and does what we endeavor to do diverge from that aim. Is our purpose to make every hue around us grey? Or to fetter ourselves in the forest of reinforced concrete? Or, to alter the earth to meet human’s multifarious demands regardless of the sustainability?

The onus is on us to maintain an agreeable environment. We need introspection, though it may seems arduous to atone for the devastation.

Fortunately, we gradually awake to the pernicious effects, and we are making a change. Eco-civilization construction has been put forward, and the departments concerned are sparing no effort to ameliorate the imbalanced ecology.

It was late spring, and every plant was flourishing. I heard the birds warbling a mellifluous night chorus, my heart mollified.

I entertain the hope that one day, we can thoroughly relish the felicity from nature. I envision an unspoiled environment. If we esteem nature, make optimal decision, and take prompt action.

I’m looking forward to the arrival of that very day.

Spring I

I heard something twittering on the balcony.

Walking on my tiptoe, I reached the window, through which I spotted a turtle dove standing on the stainless-steel-made railing, with a sprig in its beak. Barely had I observed it attentively when it hastily fled away, leaving behind a fuzzy silhouette.

I was leaning on the windowsill, looking down, when a ball of white captivated me, which I instantly recognized as peach blossoms. Bathed in radiant sunlight, they stretched their exquisite bodies in all directions. The balmy breeze gently swayed the flowers, carrying the scent they exuded wherever it passed. Eventually here came the spring. As I was about to return to my room, my eyes rested on a half-finished bird nest that lay on the board put on the railings.

It was unbelievable that the dove was planning to build a nest on our balcony. Yet this was the case. What’s more, I witnessed two doves erecting their lovely home together. Before long, a complete nest came out.

I could see in the mild twilight, a tiny nest lying steadily behind the huge flower pots, and an adorable dove perching on the nest. The setting sun cast its silky beams on this little cute creature through the verdant plants in the pots, resembling a warm hand patting tenderly. This tranquil scene was set off by the distant golden horizon, which formed the most enchanting landscape in the world. I could picture that under its little body, eggs were being warmed up, and new life was being brewed.

But we all know that good times do not last long, which I grasped on that drizzly evening when the dove had gone away and had not returned.

I stared at the empty nest vacantly, a sense of loss welling up in my heart. There lay the sole egg placidly, reflecting the cold light which the moon shed on the earth. Why didn’t the dove return? Maybe, I unintentionally frightened it, or, it had found a superior habitat. But, did it desert its cherished egg? On this fairly chilly rainy night, where could it go? Would it be shivering all over in the freezing wind? As the moonlight suffused the ground, I perceived that, never would I know the answer.

I leaned on the windowsill, overlooking the swaying flowers. I could hear raindrops knocking the canopy, composing a rumpled melody agitating my thoughts. I could see them trickling down the eaves, descending, sinking into puddles, feebly splattering a spray of droplets. Also I could hear the moaning wind dashing through the alley, wildly rattling loose windows and doors. I could see it wobbling the leaves furiously, severing them from branches and tugging them down. But, in the meantime, I could see the blossoms that the tree threw up in the air, being hit by the rain and being swung by the wind, while they kept emanating aroma that wafted all around. I could see the myriad scattered petals which fell on the moist tarmac, all colors draining rapidly, yet somehow, they were peculiarly appealing with the lamplight flickering in dewdrops.

It suddenly occurred to me that, unlike what I considered before, what was called spring, was not a picture in a particular style. It not only meant arrival and growth, but also meant departure and wilting to some extent.

So the spring is not all about a couple of turtle doves who are busy constructing a cozy neat twiggy nest where the new life will be brought, nor about the pale pink blossoms that bloom wherever you behold, adorning the corner of the bright blue sky. The essence of the spring lies in the persistence that the doves possess, even if they get so close to human beings and lose one of the only two eggs. It lies in the patience with which the dove squats in its nest unceasingly, awaiting the arrival of the baby, no matter the icy drizzle and the roaring gale. It lies in the optimism which encourages the blossoms to bloom vigorously in the frigid sprinkle. It lies in the devotion of the blossoms as they make the world transfigured despite the adversity they are confronted with.

I lingered on the balcony, hearkening wind and rain, dismay dissipating gradually. And here came the spring. I could sense that, distinctly.

The Beginning

The bell is about to ring. The new year is in sight.

Time flies as we grow and mature, leaving naivety behind. No sooner have we relived the past year in detail than we face the coming year hurriedly. Lay down the heavy burden, repack the travelling bag, take our initial dream, step forward readily and firmly.

It’s said those who have received compliments should be rather careful, for other’s acclaim may block the consciousness of weakness, which surely leads to failure. Yet by no means shall we belittle ourselves, since nobody will have confidence in us unless we are confident. Owning a distinct understanding of oneself, cultivating the better part, remedying what have been done that turns out a mistake, are ways to adjust our mentality.

When we gaze at the vast, dark night sky, sparsely-arranged stars hang high, whispering about what they have just witnessed on this tiny, lonely, but vigorous cerulean planet, such as a large amount of teenagers burning the midnight oil, waiting for that extraordinary moment, after which they may become an adult, or may bare his or her heart to someone else, or, may just sigh with the feeling that it seems something in life, which is hard to be aware of, has vanished silently and drastically. Whatever they see, the sparkling stars keep extending their best wishes to whom they have not known and may never know. The stars, the glinting stars, the stars that split the profound night sky, for how long will we still be able to look up at you?

When we overlook the pure white snowfield, accumulated snowflakes lie serenely, mirroring the warm, golden light that streetlamps cast, illumining the bottom of passers-by’s hearts, which originally filled with some annoying things, like working overtime without gaining anything, or getting into a conflict with someone he or she considers dearest, or, biting, frigid wind blowing right into cheek, as if thousands of sharp knives are pricking the skin. But at the time, the light disperses the dust, and pushes the haze in the heart aside, bringing peace and quiet, awakening hope for the future, for the year approaching. The snow, the crystal snow, the snow that decorates the empty city streets, will you still be here years later?

No matter how we get through the year just past, it doesn’t make any sense to immerse in the memory. We shall always look forward, but don’t forget, up in the sky, down on the ground, there must be someone else, caring you, supporting you, giving you hope whenever it seems impossible.

Anyhow, it’s a brand new beginning.

Goodbye, 2018. Hello, 2019.

Light III

He could feel drizzle patting his face.

To the best of his ability, he stood up. Anew, darkness enveloped him.

He pondered about what he had just experienced, which was patently a dreadful nightmare. Yet everything was so real, that he couldn’t persuade himself not to recall that dream.

In a moment, he caught sight of something in the distance, something that was gigantic enough to be seen. It was hard for him to put down the desire to approach it. He started padding, but he sensed something preternatural. As if he was walking through a long narrow corridor, in which there was solely enormously faint light.

When he finally reached the thing, he was astonished to find that he was facing a boundless wall which was clearly insurmountable. Scarcely had he had time to figure out what was happening when he was sucked into an exceedingly strong whirlpool, losing his consciousness.

He was astounded again when he opened his eyes.

In his sight there turned seasons. He could see fresh green seedlings protruding, carefree swallows singing and dancing freely in spring, and the brilliant sun creeping up gradually, tiny trees slowly growing to a huge proportion during summer, and with autumn coming the golden foliage fell, embellishing the azure blue sky, and before long clean white snowflakes began to appear, which indicated that winter had reigned the ground. It was quite amazed that he was able to recognize the various colors in no time, as well as the weathers he used to feel and the planets passing over head. What he once could only heard of was just in front of him, like an ancient but exquisite picture scroll opening before his face by degrees. Wherever his vision went, the superb landscapes appealed to him.

He was immersing himself in this fabulous world when in an instant, as if a heavy gate was opened, recollections flooded his brain.

He attempted to cast his mind back, and something mysterious occurred.

He could see his father holding him in his arms, while his mother cradled his brother, staring at two little lives, humming a familiar song he had heard for many times. He could see he and his brother climbing up and down at home, and his brother frequently lending him a hand without which he might have fallen off the table that was still higher than him. He could see the family outing, during which his relatives did not get weary of guiding him, telling him what was going on around, such as fragrant flowers dancing with colorful butterflies. So joyful was he at that time that as he thought, in his heart a knot was untied, and the ice melted.

Never had he realized that so many fantastic memories had been missing since his third birthday, for which he would have lived delightedly, appreciating the love and hope on earth, summoning up the courage and confidence, to strive for an entirely different life.

But it was said there was no sense crying over every mistake, for what had been done was unchangeable. He smiled, blandly, as he floated in the air, higher and higher.

Looking down, he saw himself, just lying, peacefully, on the snow-covered ground.


“There lies a wall, up to the heaven, down to the hell, left to infinity, right to where you cannot see.”

“This wall is called death.”

“Only when a person is confronted with this wall will he look back upon his entire life.”

Light II

He had no idea where he was.

And a strong feeling that he had never experienced before occupied his mind little by little.

The light was still there. So dim and nebulous was it that the dread of being encircled again by the darkness swiftly crept up from the bottom of his heart. Though he managed to shift his feet with all his strength, he could hardly budge an inch.

Brimming with confusion, he cast a glance at the ground, but only to find blur in his sight. Apparently he was trapped in a funny dream, which was made up of what he had sustained in reality.

Weak as it was, the light still produced warmth like the sun. He knew clearly the sensation of basking. The sunlight was gentle and mild. The feeling of extreme familiarity brought him back to his childhood.

He vaguely remembered that all around him were glee and thrill when he was a infant. Often his parents took him out, holding him in the arms, bathed in the sunshine. Pure and innocent, he enjoyed everything he heard, smelled, touched and tasted, not being aware of the fact that there existed something called vision, which he later thirsted for.

In his memories, his parents never loved him. Oh, maybe they were, before his third birthday. It was unbelievable that a three-year-old little boy could bear so many things in mind such as hearing a baby’s weep and his parents’ mirth, as well as realizing his disability, which had bothered him for what seemed like centuries. In fact, the voices kept reverberating in his mind, and continued pushing him to the verge of collapse. He considered that day a turning point of his life.

“My third birthday.” he smiled wryly, “My last birthday.”

He had no intention, and did not willing, to grumble at his parents. What made all the things transpire was his blindness. Even he felt guilty, and believed that himself was to blame. It was him that let his parents down, and that lost courage to live. It was his disadvantage that made him an object of ridicule, and that surrounded him with the deepest darkness. Had he not been blind, everyone would have lived a better life.

Few merry moments could be found in his recollections afterwards. Or maybe.. he pretended to have forgotten them?

He did not stop searching in his memories until the light began to peter out. His mental pain slightly alleviated as he pulled himself out of mind-wandering.

But an old saying told by his grandpa suddenly emerged in his brain, which sent shudder down his spine, “Only when a person is about to depart this world will he look back upon his entire life.”

For the first time in his life, his heart was seized by such unimaginable fear, and it nearly ceased bounding.

And there went off the light, silently, remaining darkness.