function jpeg_result = jpeg_compression_cycle(original)
    % Transform matrices
    dct_matrix = dctmtx(8);
    dct = @(block_struct) dct_matrix * block_struct.data * dct_matrix';
    idct = @(block_struct) dct_matrix' * block_struct.data * dct_matrix;
    
    % Quantization tables
    q_max = 255;
    q_y = ...
       [16 11 10 16 124 140 151 161;
        12 12 14 19 126 158 160 155; 
        14 13 16 24 140 157 169 156; 
        14 17 22 29 151 187 180 162; 
        18 22 37 56 168 109 103 177; 
        24 35 55 64 181 104 113 192; 
        49 64 78 87 103 121 120 101; 
        72 92 95 98 112 100 103 199];
    q_c = ...
       [17 18 24 47 99 99 99 99; 
        18 21 26 66 99 99 99 99; 
        24 26 56 99 99 99 99 99; 
        47 66 99 99 99 99 99 99; 
        99 99 99 99 99 99 99 99; 
        99 99 99 99 99 99 99 99; 
        99 99 99 99 99 99 99 99; 
        99 99 99 99 99 99 99 99];
    
    % Scale quantization matrices based on quality factor
    qf = 20;
    if qf < 50
        q_scale = floor(5000 / qf);
    else
        q_scale = 200 - 2 * qf;
    end
    q_y = round(q_y * q_scale / 100);
    q_c = round(q_c * q_scale / 100);

    % RGB to YCbCr
    ycc = rgb2ycbcr(im2double(original));
    
    % Down-sample and decimate chroma
    cb = conv2(ycc(:, :, 2), [1 1; 1 1]) ./ 4.0;
    cr = conv2(ycc(:, :, 3), [1 1; 1 1]) ./ 4.0;
    cb = cb(2 : 2 : size(cb, 1), 2 : 2 : size(cb, 2));
    cr = cr(2 : 2 : size(cr, 1), 2 : 2 : size(cr, 2));
    y = ycc(:, :, 1);
    
    % Discrete cosine transform, with scaling before quantization.
    y = blockproc(y, [8 8], dct) .* q_max;
    cb = blockproc(cb, [8 8], dct) .* q_max;
    cr = blockproc(cr, [8 8], dct) .* q_max;
    
    % Quantize DCT coefficients
    y = blockproc(y, [8 8], @(block_struct) round(round(block_struct.data) ./ q_y));
    cb = blockproc(cb, [8 8], @(block_struct) round(round(block_struct.data) ./ q_c));
    cr = blockproc(cr, [8 8], @(block_struct) round(round(block_struct.data) ./ q_c));
    
    % Dequantize DCT coefficients
    y = blockproc(y, [8 8], @(block_struct) block_struct.data .* q_y);
    cb = blockproc(cb, [8 8], @(block_struct) block_struct.data .* q_c);
    cr = blockproc(cr, [8 8], @(block_struct) block_struct.data .* q_c);
    
    % Inverse discrete cosine transform
    y = blockproc(y ./ q_max, [8 8], idct);
    cb = blockproc(cb ./ q_max, [8 8], idct);
    cr = blockproc(cr ./ q_max, [8 8], idct);
    
    % Up-sample chroma
    upsample_filter_1d = [1 3 3 1] / 4;
    upsample_filter = upsample_filter_1d' * upsample_filter_1d;
    cb = conv2(upsample_filter, upsample(upsample(padarray(cb, [1 1], 'replicate'), 2)', 2)');
    cb = cb(4 : size(cb,1) - 4, 4 : size(cb, 2) - 4);
    cr = conv2(upsample_filter, upsample(upsample(padarray(cr, [1 1], 'replicate'), 2)', 2)');
    cr = cr(4 : size(cr,1) - 4, 4 : size(cr, 2) - 4);

    % Concatenate the channels to get the resulting image.
    jpeg_result = ycbcr2rgb(cat(3, y, cb, cr));
end
