You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中能否为图片添加隐形图像水印?求具体实现代码

Invisible Image Watermarking on Android: Feasibility & Implementation

Absolutely! You can absolutely add invisible watermarks to images on Android—there are a few reliable approaches, with LSB (Least Significant Bit) steganography being the most straightforward for mobile scenarios. It modifies the least noticeable bits of image pixels to hide data, so the watermark is completely invisible to the human eye while preserving the image’s quality. For more robustness against edits (like resizing or compression), you could also use DCT-based methods (similar to how JPEG compression works), but let’s focus on the LSB method first since it’s easier to implement and understand.

Core Concept of LSB Steganography

Every RGB pixel in a bitmap has 3 color channels (Red, Green, Blue), each represented by 8 bits. The least significant bit (the 8th bit) of each channel contributes almost nothing to the pixel’s color. By replacing these bits with the bits of our watermark text, we can embed data without altering the image’s appearance.

Step-by-Step Implementation

1. Prepare the Bitmap

First, we need to convert the target image into a mutable Bitmap so we can modify its pixel data.

2. Encode the Watermark

  • Convert the watermark text into a byte array.
  • First embed the length of the watermark (as 4 bytes, 32 bits) so we know how much data to extract later.
  • Iterate through each bit of the watermark data and replace the LSB of each color channel in the bitmap’s pixels.

3. Generate the Watermarked Image

After modifying the pixel array, create a new bitmap from the updated pixels.

4. Extract the Watermark (Optional)

To verify the watermark works, we can reverse the process: read the LSBs from the watermarked image to reconstruct the original text.

Full Code Example (Kotlin)

Encoding Function (Embed Watermark)

fun embedWatermark(originalBitmap: Bitmap, watermarkText: String): Bitmap? {
    // Create a mutable copy of the original bitmap to modify pixels
    val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
    val pixelCount = mutableBitmap.width * mutableBitmap.height
    val pixels = IntArray(pixelCount)
    mutableBitmap.getPixels(pixels, 0, mutableBitmap.width, 0, 0, mutableBitmap.width, mutableBitmap.height)

    // Convert watermark to bytes, plus prepend the length (4 bytes to store size)
    val watermarkBytes = watermarkText.toByteArray(Charsets.UTF_8)
    val lengthBuffer = ByteBuffer.allocate(4).putInt(watermarkBytes.size)
    val fullData = lengthBuffer.array() + watermarkBytes

    var currentBitIndex = 0
    val totalBitsNeeded = fullData.size * 8

    // Check if there are enough pixels to hold the watermark
    if (totalBitsNeeded > pixelCount * 3) { // 3 channels per pixel
        return null // Not enough space in the image
    }

    for (byte in fullData) {
        for (bitPosition in 0..7) {
            val pixelIndex = currentBitIndex / 3
            val channelIndex = currentBitIndex % 3 // 0=Red, 1=Green, 2=Blue

            val currentPixel = pixels[pixelIndex]
            // Extract the current channel's value
            var channelValue = when (channelIndex) {
                0 -> (currentPixel shr 16) and 0xFF
                1 -> (currentPixel shr 8) and 0xFF
                else -> currentPixel and 0xFF
            }

            // Set the LSB to the current bit of the watermark byte
            val bitToEmbed = (byte shr (7 - bitPosition)) and 0x01
            channelValue = (channelValue and 0xFE) or bitToEmbed

            // Update the pixel with the modified channel value
            val updatedPixel = when (channelIndex) {
                0 -> (currentPixel and 0x00FFFF) or (channelValue shl 16)
                1 -> (currentPixel and 0xFF00FF) or (channelValue shl 8)
                else -> (currentPixel and 0xFFFF00) or channelValue
            }

            pixels[pixelIndex] = updatedPixel
            currentBitIndex++
        }
    }

    // Apply the modified pixels back to the bitmap
    mutableBitmap.setPixels(pixels, 0, mutableBitmap.width, 0, 0, mutableBitmap.width, mutableBitmap.height)
    return mutableBitmap
}

Decoding Function (Extract Watermark)

fun extractWatermark(watermarkedBitmap: Bitmap): String? {
    val pixelCount = watermarkedBitmap.width * watermarkedBitmap.height
    val pixels = IntArray(pixelCount)
    watermarkedBitmap.getPixels(pixels, 0, watermarkedBitmap.width, 0, 0, watermarkedBitmap.width, watermarkedBitmap.height)

    var currentBitIndex = 0

    // First, extract the length of the watermark (4 bytes = 32 bits)
    val lengthBytes = ByteArray(4)
    for (byteIndex in lengthBytes.indices) {
        var byte = 0
        for (bitPosition in 0..7) {
            if (currentBitIndex >= pixelCount * 3) {
                return null // Invalid watermark or corrupted image
            }
            val pixelIndex = currentBitIndex / 3
            val channelIndex = currentBitIndex % 3

            val currentPixel = pixels[pixelIndex]
            val channelValue = when (channelIndex) {
                0 -> (currentPixel shr 16) and 0xFF
                1 -> (currentPixel shr 8) and 0xFF
                else -> currentPixel and 0xFF
            }

            // Get the LSB from the channel
            val extractedBit = channelValue and 0x01
            byte = byte or (extractedBit shl (7 - bitPosition))
            currentBitIndex++
        }
        lengthBytes[byteIndex] = byte
    }

    val watermarkLength = ByteBuffer.wrap(lengthBytes).int
    // Check if the length is reasonable
    if (watermarkLength <= 0 || watermarkLength * 8 > (pixelCount * 3) - 32) {
        return null
    }

    // Now extract the actual watermark bytes
    val watermarkBytes = ByteArray(watermarkLength)
    for (byteIndex in watermarkBytes.indices) {
        var byte = 0
        for (bitPosition in 0..7) {
            if (currentBitIndex >= pixelCount * 3) {
                return null
            }
            val pixelIndex = currentBitIndex / 3
            val channelIndex = currentBitIndex % 3

            val currentPixel = pixels[pixelIndex]
            val channelValue = when (channelIndex) {
                0 -> (currentPixel shr 16) and 0xFF
                1 -> (currentPixel shr 8) and 0xFF
                else -> currentPixel and 0xFF
            }

            val extractedBit = channelValue and 0x01
            byte = byte or (extractedBit shl (7 - bitPosition))
            currentBitIndex++
        }
        watermarkBytes[byteIndex] = byte
    }

    return String(watermarkBytes, Charsets.UTF_8)
}

Key Notes & Limitations

  • Robustness: LSB watermarks are fragile—if you save the image as a compressed format (like JPEG) or edit it (resize, filter), the watermark may be lost. For more resilience, look into DCT-based watermarking (which works in the frequency domain of the image).
  • Bitmap Config: Always use ARGB_8888 for the bitmap, as it provides 8 bits per color channel. Lower configs like RGB_565 don’t have enough bits to support LSB modifications.
  • Permissions: If you’re loading/saving images from external storage, make sure to request the necessary permissions (for Android 13+, use READ_MEDIA_IMAGES and WRITE_MEDIA_IMAGES).

内容的提问来源于stack exchange,提问作者Raja Ixrar Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:52:32