Please bookmark this page to avoid losing your image tool!

Image 4K Enhancement Tool For Realistic Skin Texture

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, upscaleFactor = 2, sharpening = 0.5, wrinkles = 0.15, pores = 12) {
    /**
     * Helper Class: Perlin Noise Generator
     * This class creates procedural noise used for generating the wrinkle texture.
     * It's embedded here to make the function self-contained.
     * Original source: https://gist.github.com/banksean/304522 (Public Domain)
     * Modernized to ES6 class syntax.
     */
    class PerlinNoise {
        constructor() {
            this.p = new Uint8Array(512);
            this.PERLIN_YWRAPB = 4;
            this.PERLIN_YWRAP = 1 << this.PERLIN_YWRAPB;
            this.PERLIN_ZWRAPB = 8;
            this.PERLIN_ZWRAP = 1 << this.PERLIN_ZWRAPB;
            this.PERLIN_SIZE = 4095;
            this.perlin_octaves = 4;
            this.perlin_amp_falloff = 0.5;
        }

        noiseDetail(lod, falloff) {
            if (lod > 0) this.perlin_octaves = lod;
            if (falloff > 0) this.perlin_amp_falloff = falloff;
        }

        seed(seed) {
            const lcg = (() => {
                let m = 4294967296, a = 1664525, c = 1013904223, s = seed;
                return () => (s = (a * s + c) % m) / m;
            })();
            for (let i = 0; i < 256; i++) {
                this.p[i] = Math.floor(lcg() * 256);
            }
            for (let i = 0; i < 256; i++) {
                this.p[i + 256] = this.p[i];
            }
        }

        fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
        lerp(t, a, b) { return a + t * (b - a); }
        grad(hash, x, y, z) {
            let h = hash & 15;
            let u = h < 8 ? x : y;
            let v = h < 4 ? y : h === 12 || h === 14 ? x : z;
            return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
        }

        noise(x, y = 0, z = 0) {
            if (this.p[0] == null) this.seed(Math.random());
            let xi = Math.floor(x) & this.PERLIN_SIZE;
            let yi = Math.floor(y) & this.PERLIN_SIZE;
            let zi = Math.floor(z) & this.PERLIN_SIZE;
            let xf = x - Math.floor(x);
            let yf = y - Math.floor(y);
            let zf = z - Math.floor(z);
            let rxf, ryf;
            let r = 0;
            let ampl = 0.5;
            let n1, n2, n3;

            for (let o = 0; o < this.perlin_octaves; o++) {
                let of = xi + (yi << this.PERLIN_YWRAPB) + (zi << this.PERLIN_ZWRAPB);
                rxf = this.fade(xf);
                ryf = this.fade(yf);
                n1 = this.grad(this.p[of & this.PERLIN_SIZE], xf, yf, zf);
                n1 += rxf * (this.grad(this.p[(of + 1) & this.PERLIN_SIZE], xf - 1, yf, zf) - n1);
                n2 = this.grad(this.p[(of + this.PERLIN_YWRAP) & this.PERLIN_SIZE], xf, yf - 1, zf);
                n2 += rxf * (this.grad(this.p[(of + this.PERLIN_YWRAP + 1) & this.PERLIN_SIZE], xf - 1, yf - 1, zf) - n2);
                n1 += ryf * (n2 - n1);
                of += this.PERLIN_ZWRAP;
                n2 = this.grad(this.p[of & this.PERLIN_SIZE], xf, yf, zf - 1);
                n2 += rxf * (this.grad(this.p[(of + 1) & this.PERLIN_SIZE], xf - 1, yf, zf - 1) - n2);
                n3 = this.grad(this.p[(of + this.PERLIN_YWRAP) & this.PERLIN_SIZE], xf, yf - 1, zf - 1);
                n3 += rxf * (this.grad(this.p[(of + this.PERLIN_YWRAP + 1) & this.PERLIN_SIZE], xf - 1, yf - 1, zf - 1) - n3);
                n2 += ryf * (n3 - n2);
                n1 += this.fade(zf) * (n2 - n1);
                r += n1 * ampl;
                ampl *= this.perlin_amp_falloff;
                xi <<= 1; xf *= 2;
                yi <<= 1; yf *= 2;
                zi <<= 1; zf *= 2;
                if (xf >= 1.0) {xi++; xf--;}
                if (yf >= 1.0) {yi++; yf--;}
                if (zf >= 1.0) {zi++; zf--;}
            }
            return r;
        }
    }

    // 1. SETUP
    // The "4K" part is simulated by upscaling. A factor of 2 is a good balance of quality and performance.
    const targetWidth = originalImg.width * upscaleFactor;
    const targetHeight = originalImg.height * upscaleFactor;

    const canvas = document.createElement('canvas');
    canvas.width = targetWidth;
    canvas.height = targetHeight;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // 2. UPSCALE IMAGE
    // Draw the original image onto the larger canvas. The browser performs interpolation.
    ctx.drawImage(originalImg, 0, 0, targetWidth, targetHeight);

    // 3. SHARPENING (Unsharp Mask)
    // To counteract the blur from upscaling, we apply a sharpening filter.
    if (sharpening > 0) {
        const blurCanvas = document.createElement('canvas');
        blurCanvas.width = targetWidth;
        blurCanvas.height = targetHeight;
        const blurCtx = blurCanvas.getContext('2d', { willReadFrequently: true });
        
        // Create a blurred version of the upscaled image
        blurCtx.filter = `blur(${1 * upscaleFactor}px)`;
        blurCtx.drawImage(canvas, 0, 0);
        
        const originalData = ctx.getImageData(0, 0, targetWidth, targetHeight);
        const blurData = blurCtx.getImageData(0, 0, targetWidth, targetHeight).data;
        const oData = originalData.data;

        for (let i = 0; i < oData.length; i += 4) {
             // Calculate luminance to avoid color shifts during sharpening
            const originalLuminance = 0.299 * oData[i] + 0.587 * oData[i + 1] + 0.114 * oData[i + 2];
            const blurLuminance = 0.299 * blurData[i] + 0.587 * blurData[i + 1] + 0.114 * blurData[i + 2];
            
            const diff = originalLuminance - blurLuminance;
            const sharpeningEffect = diff * sharpening;

            // Apply sharpening diff to all color channels
            oData[i] = Math.max(0, Math.min(255, oData[i] + sharpeningEffect));
            oData[i + 1] = Math.max(0, Math.min(255, oData[i + 1] + sharpeningEffect));
            oData[i + 2] = Math.max(0, Math.min(255, oData[i + 2] + sharpeningEffect));
        }
        ctx.putImageData(originalData, 0, 0);
    }
    
    // 4. WRINKLE TEXTURE (Perlin Noise)
    // We generate a procedural texture and blend it over the image to simulate larger skin details.
    if (wrinkles > 0) {
        const perlin = new PerlinNoise();
        perlin.seed(Math.random());

        const textureCanvas = document.createElement('canvas');
        textureCanvas.width = targetWidth;
        textureCanvas.height = targetHeight;
        const textureCtx = textureCanvas.getContext('2d', { willReadFrequently: true });
        const textureData = textureCtx.createImageData(targetWidth, targetHeight);
        const tData = textureData.data;
        
        // Scale determines the "size" of the wrinkles
        const wrinkleScaleX = 0.03 / upscaleFactor;
        const wrinkleScaleY = 0.05 / upscaleFactor; // Slightly different scales for more natural look


        for (let y = 0; y < targetHeight; y++) {
            for (let x = 0; x < targetWidth; x++) {
                // Get a noise value between -1 and 1
                let noise = perlin.noise(x * wrinkleScaleX, y * wrinkleScaleY, 0);
                // Convert to a grayscale value in the 0-255 range
                const value = (noise * 0.5 + 0.5) * 255;
                const grayValue = 128 + (value - 128); 
                
                const index = (y * targetWidth + x) * 4;
                tData[index] = grayValue;
                tData[index + 1] = grayValue;
                tData[index + 2] = grayValue;
                tData[index + 3] = 255;
            }
        }
        textureCtx.putImageData(textureData, 0, 0);
        
        // Blend the texture over the main image
        ctx.globalCompositeOperation = 'overlay';
        ctx.globalAlpha = wrinkles;
        ctx.drawImage(textureCanvas, 0, 0);
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 1.0;
    }

    // 5. PORES (Fine-grained noise)
    // We add a final layer of random noise to simulate skin pores and fine imperfections.
    if (pores > 0) {
        const finalImageData = ctx.getImageData(0, 0, targetWidth, targetHeight);
        const fData = finalImageData.data;
        for (let i = 0; i < fData.length; i += 4) {
            // Add monochromatic noise to avoid colorful speckles
            const noise = (Math.random() - 0.5) * pores;
            fData[i]   = Math.max(0, Math.min(255, fData[i] + noise));
            fData[i+1] = Math.max(0, Math.min(255, fData[i+1] + noise));
            fData[i+2] = Math.max(0, Math.min(255, fData[i+2] + noise));
        }
        ctx.putImageData(finalImageData, 0, 0);
    }

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image 4K Enhancement Tool for Realistic Skin Texture is designed to upscale images while enhancing skin textures. It allows users to increase the resolution of images, apply sharpening effects to counteract blurriness, and add realistic skin details such as wrinkles and pores. This tool is ideal for photographers, graphic designers, and digital artists who want to improve the quality of portraits and close-up skin images, ensuring that the final output retains lifelike characteristics even at higher resolutions.

Leave a Reply

Your email address will not be published. Required fields are marked *