// Filename:- image.cpp // #include "stdafx.h" #include "includes.h" #include "modview.h" #include "textures.h" // #include "image.h" #ifndef DWORD #define DWORD unsigned long #endif #define iDSTAMP_CELL_PIXDIM 16 // adjust this, higher = more secure but less stamps on picture #define iDSTAMP_CELL_PIXELS (iDSTAMP_CELL_PIXDIM * iDSTAMP_CELL_PIXDIM) #define iDSTAMP_INSTANCE_CELLDIM 8 // leave this alone at all times! #define iDSTAMP_INSTANCE_PIXDIM (iDSTAMP_INSTANCE_CELLDIM * iDSTAMP_CELL_PIXDIM) #define iDSTAMP_CHAR_BITS 7 #define iHIDDENBIT 1//32 // 1,2,4,8,16,32,64, or 128 #define iHIDDENBITSHIFT 0//5 // 0,1,2,3, 4 5 6 7 #pragma pack(push,1) typedef struct { char sHDR[3]; // "HDR", no trailing zero char sDDMMYY[6]; // date/month/year, no trailing zero char sText[14]; // text, no end trailing zero, but maybe early one } WatermarkData_t; typedef struct { WatermarkData_t Data; DWORD dwCRC; } Watermark_t; typedef struct { byte R,G,B,A; }Pixel_t; #pragma pack(pop) typedef struct { byte *pPixels; int iWidth; int iHeight; int iPlanes; } DStampImage_t; typedef struct { DStampImage_t *pImage; Watermark_t *pWatermark; } DStampData_t; Watermark_t TheWatermark; #define PixelSetR(p,r) p.R=r #define PixelSetG(p,g) p.G=g #define PixelSetB(p,b) p.B=b #define PixelSetA(p,a) p.A=a #define PixelGetR(p) p.R #define PixelGetG(p) p.G #define PixelGetB(p) p.B #define PixelGetA(p) p.A #define PixelInit(p) memset(&p,0,sizeof(p)); // later on I'll adapt this for initing default alpha etc, but for now #define PROGRESS_INIT \ CProgressCtrl *pProgress = NULL; \ if (((CModViewApp*)AfxGetApp())->m_pMainWnd) \ { \ pProgress = new CProgressCtrl; \ bool bOK = !!pProgress->Create( WS_CHILD|WS_VISIBLE|PBS_SMOOTH, \ CRect(100,100,200,200), \ ((CModViewApp*)AfxGetApp())->m_pMainWnd, \ 1 \ ); \ if (!bOK) \ { \ delete pProgress; \ pProgress = NULL; \ } \ } #define PROGRESS_SETRANGE(range) \ if (pProgress) \ { \ pProgress->SetRange(0,range); \ } #define PROGRESS_SETPOS(position) \ if (pProgress) \ { \ pProgress->SetPos(position);\ } #define PROGRESS_CLOSE \ if (pProgress) \ { \ delete pProgress; \ pProgress = 0; \ } // return NULL = legal, else string saying why not... // static LPCSTR DStamp_TextIsLegal(LPCSTR psText) { int iLen = strlen(psText); if (iLen > sizeof(TheWatermark.Data.sText)) { return va("\"%s\" is too long by %d chars",psText, sizeof(TheWatermark.Data.sText) - iLen); } for (int i=0; i 127) { return va("\"%s\" contains hi-ascii char '%c', only 7-bit ascii allowed",psText, psText[i]); } } return NULL; } static void DStamp_EncryptData(void *pvData, int iByteLength) { /* don't use this method since we need to keep as 7-bit ascii byte *pData = (byte*) pvData; for (int i=0; iData.sHDR, "HDR", sizeof(pWatermark->Data.sHDR)); strncpy(pWatermark->Data.sText, psText, sizeof(pWatermark->Data.sText)); strncpy(pWatermark->Data.sDDMMYY, DStamp_GetYear(), sizeof(pWatermark->Data.sDDMMYY)); DStamp_EncryptData(&pWatermark->Data, sizeof(pWatermark->Data)); pWatermark->dwCRC = DStamp_CalcBlockCRC(&pWatermark->Data, sizeof(pWatermark->Data)); return pWatermark; } static Pixel_t *DStamp_GetImagePixel(DStampImage_t *pImage, int iXpos, int iYpos) { int iBPP = (pImage->iPlanes == 24)?3:4; byte *pPixels = pImage->pPixels + (iYpos * pImage->iWidth * iBPP) + (iXpos * iBPP); static Pixel_t Pixel; memcpy(&Pixel, pPixels, iBPP); if (iBPP == 3) { Pixel.A = 255; // actually, this can be ignored } return &Pixel; } static void DStamp_SetImagePixel(DStampImage_t *pImage, int iXpos, int iYpos, Pixel_t *pPixel) { int iBPP = (pImage->iPlanes == 24)?3:4; byte *pPixels = pImage->pPixels + (iYpos * pImage->iWidth * iBPP) + (iXpos * iBPP); memcpy(pPixels, pPixel, iBPP); } // returns either 0 or 1 for bits, or 2 for "finished" // static int DStamp_GetWaterMarkBit(int *piSourceIndex_Byte, int *piSourceIndex_Bit, void *pvBytes, int iBytesLen) { byte *pBytes = (byte *) pvBytes; // if (*piSourceIndex_Byte==(int)strlen(psWatermarkString)+1) // trying to get bits from beyond trailing zero? // return 2; if (*piSourceIndex_Byte==iBytesLen) // trying to get bits from beyond trailing zero? return 2; char c = pBytes[*piSourceIndex_Byte]; int iBitReturn = (c>>*piSourceIndex_Bit)&1; *piSourceIndex_Bit+=1; // *not* ++! if (*piSourceIndex_Bit == iDSTAMP_CHAR_BITS) { *piSourceIndex_Bit = 0; *piSourceIndex_Byte+=1; // *not* ++! } return iBitReturn; } static void DStamp_ReadCell(DStampData_t *pData, int iCellXpos, int iCellYpos, byte *b1, byte *b2, byte *b3) { int iWBitR = 0, iWBitG = 0, iWBitB = 0; for (int iPixelY=0; iPixelYpImage, iPixelX + iCellXpos, iPixelY + iCellYpos); iWBitR += (PixelGetR(Pixel)&iHIDDENBIT)?1:0; iWBitG += (PixelGetG(Pixel)&iHIDDENBIT)?1:0; iWBitB += (PixelGetB(Pixel)&iHIDDENBIT)?1:0; } } // work out pixel consensuses... (consensi?) // // if > half the pixels are true, accept that as true... // *b1 = (iWBitR > iDSTAMP_CELL_PIXELS/2)?1:0; *b2 = (iWBitG > iDSTAMP_CELL_PIXELS/2)?1:0; *b3 = (iWBitB > iDSTAMP_CELL_PIXELS/2)?1:0; } static void DStamp_MarkCell(DStampData_t *pData, int iCellXpos, int iCellYpos, int *piTxtByte, int *piTxtBit) { byte R = DStamp_GetWaterMarkBit(piTxtByte, piTxtBit, pData->pWatermark, sizeof(*pData->pWatermark)); byte G = DStamp_GetWaterMarkBit(piTxtByte, piTxtBit, pData->pWatermark, sizeof(*pData->pWatermark)); byte B = DStamp_GetWaterMarkBit(piTxtByte, piTxtBit, pData->pWatermark, sizeof(*pData->pWatermark)); for (int iPixelY=0; iPixelYpImage, iPixelX + iCellXpos, iPixelY + iCellYpos); PixelSetR(Pixel,( (PixelGetR(Pixel)&~iHIDDENBIT) | (((R==2)?0:R)<pImage, iPixelX + iCellXpos, iPixelY + iCellYpos, &Pixel); } } /* byte R2,G2,B2; DStamp_ReadCell(pData, iCellXpos, iCellYpos, &R2,&G2,&B2); assert( (R2==((R==2)?0:R)) && (G2==((G==2)?0:G)) && (B2==((B==2)?0:B)) ); */ } // apply one watermark-instance to the image... // static void DStamp_MarkInstance(DStampData_t *pData, int iInstancePixelX, int iInstancePixelY) { int iTxtByte = 0; int iTxtBit = 0; for (int iYCell = 0; iYCell < iDSTAMP_INSTANCE_CELLDIM; iYCell++) { for (int iXCell = 0; iXCell < iDSTAMP_INSTANCE_CELLDIM; iXCell++) { int iCellXpos = iInstancePixelX + (iXCell * iDSTAMP_CELL_PIXDIM); int iCellYpos = iInstancePixelY + (iYCell * iDSTAMP_CELL_PIXDIM); DStamp_MarkCell(pData, iCellXpos, iCellYpos, &iTxtByte, &iTxtBit); } } } // see if we can read some watermark data, else return NULL... // static LPCSTR DStamp_ReadInstance(DStampData_t *pData, int iInstancePixelX, int iInstancePixelY) { LPCSTR psMessage = NULL; int iTxtByte = 0; int iTxtBit = 0; byte *pbOut = (byte*) pData->pWatermark; memset(pbOut,0,sizeof(*pData->pWatermark)); for (int iYCell = 0; iYCell < iDSTAMP_INSTANCE_CELLDIM; iYCell++) { for (int iXCell = 0; iXCell < iDSTAMP_INSTANCE_CELLDIM; iXCell++) { int iCellXpos = iInstancePixelX + (iXCell * iDSTAMP_CELL_PIXDIM); int iCellYpos = iInstancePixelY + (iYCell * iDSTAMP_CELL_PIXDIM); byte b1,b2,b3; DStamp_ReadCell(pData, iCellXpos, iCellYpos, &b1,&b2,&b3); #define DECODEBIT(DestString,bit) \ DestString[iTxtByte] |= bit<=3) // huge speed opt, check for header, if not found, give up on this cell... { if (strncmp((char*)pbOut,"HDR",3)) { return NULL; } } } } if (!strncmp(pData->pWatermark->Data.sHDR,"HDR",3)) { DWORD dwCRC = DStamp_CalcBlockCRC(&pData->pWatermark->Data, sizeof(pData->pWatermark->Data)); char sString[100]; char sDate[100]; strncpy(sString,pData->pWatermark->Data.sText,sizeof(pData->pWatermark->Data.sText)); sString[sizeof(pData->pWatermark->Data.sText)] = '\0'; strncpy(sDate,pData->pWatermark->Data.sDDMMYY,sizeof(pData->pWatermark->Data.sDDMMYY)); sDate[sizeof(pData->pWatermark->Data.sDDMMYY)] = '\0'; static char sOutput[1024]; sprintf(sOutput,"SentTo: \"%s\", Date(DD/MM/YY) = %s",sString,sDate); if (dwCRC == pData->pWatermark->dwCRC) { OutputDebugString(sOutput); psMessage = &sOutput[0]; } else { OutputDebugString(va("Skipping non-CRC HDR-match: %s\n",sOutput)); } } return psMessage; } // return is NULL for ok, else error string... // LPCSTR DStamp_MarkImage(byte *pPixels, int iWidth, int iHeight, int iPlanes, LPCSTR psText) { LPCSTR psError = NULL; if (iPlanes == 24 || iPlanes == 32) { psError = DStamp_TextIsLegal(psText); if (!psError) { DStampImage_t Image; Image.pPixels = pPixels; Image.iWidth = iWidth; Image.iHeight = iHeight; Image.iPlanes = iPlanes; DStampData_t Data; Data.pImage = &Image; Data.pWatermark = DStamp_InitWatermark(psText); int iInstances_Across = Data.pImage->iWidth / iDSTAMP_INSTANCE_PIXDIM; int iInstances_Down = Data.pImage->iHeight / iDSTAMP_INSTANCE_PIXDIM; int iInstancesTotal = iInstances_Across * iInstances_Down; #ifdef _DEBUG int iDebug_WaterMarkSize = sizeof(Watermark_t); OutputDebugString(va("%d stamp instances on screen\n",iInstancesTotal)); #endif if (iInstancesTotal) { // center the stamp grid within the image... // int iYStart = (Data.pImage->iHeight - (iInstances_Down * iDSTAMP_INSTANCE_PIXDIM))/2; int iXStart = (Data.pImage->iWidth - (iInstances_Across * iDSTAMP_INSTANCE_PIXDIM))/2; for (int iInstanceY = 0; iInstanceY < iInstances_Down; iInstanceY++) { for (int iInstanceX = 0; iInstanceX < iInstances_Across; iInstanceX++) { int iInstancePixelX = (iInstanceX * iDSTAMP_INSTANCE_PIXDIM) + iXStart; int iInstancePixelY = (iInstanceY * iDSTAMP_INSTANCE_PIXDIM) + iYStart; DStamp_MarkInstance(&Data, iInstancePixelX, iInstancePixelY); // LPCSTR psMessage = DStamp_ReadInstance(&Data, iInstancePixelX, iInstancePixelY); // if (psMessage) // { // OutputDebugString(va("Marked Instance with message \"%s\"\n",psMessage)); // } // else // { // int z=1; // } } } } else { psError = va("DStamp_MarkImage(): Unable to fit watermark on screen using iDSTAMP_CELL_PIXDIM of %d!",iDSTAMP_CELL_PIXDIM); } } } else { psError = "DStamp_MarkImage(): Supplied image must be 24 or 32 bit"; } return psError; } bool DStamp_MarkImage(Texture_t *pTexture, LPCSTR psText) { LPCSTR psError = DStamp_MarkImage(pTexture->pPixels, pTexture->iWidth, pTexture->iHeight, 32, psText); if (!psError) return true; ErrorBox(psError); return false; } void DStamp_AnalyseImage(byte *pPixels, int iWidth, int iHeight, int iPlanes) { CWaitCursor wait; const int iBlockSampleSize = 8; PROGRESS_INIT; PROGRESS_SETRANGE((iHeight/iBlockSampleSize)); DStampImage_t Image; Image.pPixels = pPixels; Image.iWidth = iWidth; Image.iHeight = iHeight; Image.iPlanes = iPlanes; DStampImage_t ImageOut; ImageOut.pPixels = (byte *) malloc (iWidth * iHeight * ((iPlanes == 24)?3:4)); ImageOut.iWidth = iWidth; ImageOut.iHeight = iHeight; ImageOut.iPlanes = iPlanes; /* // new line dup code... // for (int y=0; y half blocks noisy // for (int by=0; by < iBlockSampleSize; by++) { for (int bx=0; bx < iBlockSampleSize; bx++) { int px = x+bx; int py = y+by; Pixel_t *pPixel = DStamp_GetImagePixel(&Image, px, py); switch (iBlockPass) { case 0: // read/accumulate pixel values... { r += pPixel->R; g += pPixel->G; b += pPixel->B; rh = max(rh,pPixel->R); gh = max(gh,pPixel->G); bh = max(bh,pPixel->B); rl = min(rl,pPixel->R); gl = min(gl,pPixel->G); bl = min(bl,pPixel->B); } break; case 1: // analyse... { if (rh-rl>90 || gh-gl>90 || bh-bl>90 ) { iMassivelyDeviantPixels++; } if (px>0 && px0 && pyR; int this_g = pPixel->G; int this_b = pPixel->B; bool bPixelIsNoisy = false; if (px==123 && py==235) { int z=1; } for (int y2=py-1; y2R - this_r); int gdiff = abs(pPixel->G - this_g); int bdiff = abs(pPixel->B - this_b); const int iMinTolerance = 7; const int iMaxTolerance = 50; if ((rdiff > iMaxTolerance) || (gdiff > iMaxTolerance) || (bdiff > iMaxTolerance) || abs(pPixel->R - r) > iMaxTolerance || abs(pPixel->G - g) > iMaxTolerance || abs(pPixel->B - b) > iMaxTolerance ) { //iMassivelyDeviantPixels++; iNoisyPixels-=5000; // force not to be noisy for blocks with sharp contrasts } else if ((rdiff > iMinTolerance && rdiff < iMaxTolerance) || (gdiff > iMinTolerance && gdiff < iMaxTolerance) || (bdiff > iMinTolerance && bdiff < iMaxTolerance) ) { bPixelIsNoisy = true; } } } } if (bPixelIsNoisy) { iNoisyPixels++; } } } break; case 2: // paint... { pPixel->R = pPixel->G = pPixel->B = (iNoisyPixels>((iBlockSampleSize*iBlockSampleSize)/2))?255:0; DStamp_SetImagePixel(&ImageOut, x+bx, y+by, pPixel); } break; } } } switch (iBlockPass) { case 0: r /= iBlockSampleSize*iBlockSampleSize; g /= iBlockSampleSize*iBlockSampleSize; b /= iBlockSampleSize*iBlockSampleSize; break; case 1: if (iMassivelyDeviantPixels > (iBlockSampleSize*iBlockSampleSize)/8) { // if > 1/8th of the pixels are massively deviant, then set this block so we can't use it. iNoisyPixels-=5000; // force not to be noisy for blocks with sharp contrasts } break; } } } } #endif #if 1 // copy analysis results over input picture for saving on return... // DStampImage_t ImageTmp; ImageTmp.pPixels = (byte *) malloc (iWidth * iHeight * ((iPlanes == 24)?3:4)); ImageTmp.iWidth = iWidth; ImageTmp.iHeight = iHeight; ImageTmp.iPlanes = iPlanes; memcpy(ImageTmp.pPixels,ImageOut.pPixels, iWidth * iHeight * ((iPlanes == 24)?3:4)); // now eliminate any single white or black sampleblocks... // // for (int iEliminate = 0; iEliminate<2; iEliminate++) { // start one-in from all edges, so we can check 8 surrounding squares safely... // for (y = 0+iBlockSampleSize; y + (2*iBlockSampleSize) < iHeight; y += iBlockSampleSize) { for (int x = 0+iBlockSampleSize; x + (2*iBlockSampleSize) < iWidth; x+= iBlockSampleSize) { unsigned int r=0,g=0,b=0; Pixel_t *pPixel = DStamp_GetImagePixel(&ImageTmp, x, y); bool bThisPixelWasBlack = !(pPixel->R); // all channels same, so just checking R is fine //if (bThisPixelWasBlack) { // now check neighbours, all 8 surrounding blocks must be same colour, or bye-bye... // bool bSurroundedByOpposite = true; for (int y2=y-iBlockSampleSize; y2R); if (bScanPixelWasBlack == bThisPixelWasBlack) { bSurroundedByOpposite = false; } } } } if (bSurroundedByOpposite) { // blot this pixel out by toggling it to the other colour... // pPixel->R = pPixel->G = pPixel->B = bThisPixelWasBlack?255:0; for (int py=y; pyR); // all channels same, so just checking R is fine // bool bPrevPixelWasBlack = true; // if (x>=iBlockSampleSize) // { // pPixel = DStamp_GetImagePixel(&ImageTmp, x-iBlockSampleSize, y); // bPrevPixelWasBlack = !(pPixel->R); // all channels same, so just checking R is fine // } if (!bThisPixelWasBlack )//&& !bPrevPixelWasBlack) { unsigned int r=0,g=0,b=0; for (int iPass=0; iPass<2; iPass++) { // highlight this sample square of the original pic... // for (int y2=y; y2R; g+= pPixel->G; b+= pPixel->B; } else { #if 1 // highlight... // pPixel = DStamp_GetImagePixel(&Image, x2/*-iBlockSampleSize*/, y2); pPixel->R |= 128; pPixel->G &=~128; pPixel->B &=~128; #else pPixel = DStamp_GetImagePixel(&Image, x2, y2); pPixel->R = (r + pPixel->R)/2; pPixel->G = (g + pPixel->G)/2; pPixel->B = (b + pPixel->B)/2; #endif DStamp_SetImagePixel(&Image, x2, y2, pPixel); } } } if (!iPass) { pPixel->R = r = r/(iBlockSampleSize*iBlockSampleSize); pPixel->G = g = g/(iBlockSampleSize*iBlockSampleSize); pPixel->B = b = b/(iBlockSampleSize*iBlockSampleSize); } } } } } PROGRESS_CLOSE; free(ImageTmp.pPixels); free(ImageOut.pPixels); } void DStamp_AnalyseImage(Texture_t *pTexture) { DStamp_AnalyseImage(pTexture->pPixels, pTexture->iWidth, pTexture->iHeight, 32); } LPCSTR DStamp_ReadImage(byte *pPixels, int iWidth, int iHeight, int iPlanes) { CWaitCursor wait; LPCSTR psMessage = NULL; if (iPlanes == 24 || iPlanes == 32) { DStampImage_t Image; Image.pPixels = pPixels; Image.iWidth = iWidth; Image.iHeight = iHeight; Image.iPlanes = iPlanes; DStampData_t Data; Data.pImage = &Image; Data.pWatermark = &TheWatermark; if (1) { int iInstances_Across = Data.pImage->iWidth / iDSTAMP_INSTANCE_PIXDIM; int iInstances_Down = Data.pImage->iHeight / iDSTAMP_INSTANCE_PIXDIM; // #ifdef _DEBUG // int iDebug_WaterMarkSize = sizeof(Watermark_t); // int iDebug_InstancesTotal = iInstances_Across * iInstances_Down; // #endif /* #define iDSTAMP_CELL_PIXDIM 16 // adjust this, higher = more secure but less stamps on picture #define iDSTAMP_CELL_PIXELS (iDSTAMP_CELL_PIXDIM * iDSTAMP_CELL_PIXDIM) #define iDSTAMP_INSTANCE_CELLDIM 8 // leave this alone at all times! #define iDSTAMP_INSTANCE_PIXDIM (iDSTAMP_INSTANCE_CELLDIM * iDSTAMP_CELL_PIXDIM) #define iDSTAMP_CHAR_BITS 7 */ CProgressCtrl *pProgress = NULL; if (((CModViewApp*)AfxGetApp())->m_pMainWnd) { pProgress = new CProgressCtrl; bool bOK = !!pProgress->Create( WS_CHILD|WS_VISIBLE|PBS_SMOOTH, // DWORD dwStyle, CRect(100,100,200,200), // const RECT& rect, ((CModViewApp*)AfxGetApp())->m_pMainWnd, // CWnd* pParentWnd, 1 // UINT nID ); if (!bOK) { delete pProgress; pProgress = NULL; } } // the image may have been cropped, so we need to hunt for the signature at every pixel position... // int iYScans = 0; for (int iLazy = 0; iLazy<2; iLazy++) { for (int iYStart = 0; iYStart < iDSTAMP_INSTANCE_PIXDIM && !psMessage; iYStart++) { if (iLazy) { if (pProgress) { pProgress->SetRange(0,iYScans); } OutputDebugString(va("iYStart %%%d\n",(iYStart*100)/iDSTAMP_INSTANCE_PIXDIM)); if (pProgress) { pProgress->SetPos(iYStart); // wait.Restore(); } for (int iXStart = 0; iXStart < iDSTAMP_INSTANCE_PIXDIM && !psMessage; iXStart++) { // look for some stamps here... // for (int iInstanceY = 0; iInstanceY < iInstances_Down && !psMessage; iInstanceY++) { for (int iInstanceX = 0; iInstanceX < iInstances_Across && !psMessage; iInstanceX++) { int iInstancePixelX = (iInstanceX * iDSTAMP_INSTANCE_PIXDIM) + iXStart; int iInstancePixelY = (iInstanceY * iDSTAMP_INSTANCE_PIXDIM) + iYStart; if (iInstancePixelX + iDSTAMP_INSTANCE_PIXDIM >= iWidth || iInstancePixelY + iDSTAMP_INSTANCE_PIXDIM >= iHeight ) { // ... then skip this position while hunting for header start } else { psMessage = DStamp_ReadInstance(&Data, iInstancePixelX, iInstancePixelY); } } } } } else { for (int iInstanceY = 0; iInstanceY < iInstances_Down; iInstanceY++) { int iInstancePixelY = (iInstanceY * iDSTAMP_INSTANCE_PIXDIM) + iYStart; if (iInstancePixelY + iDSTAMP_INSTANCE_PIXDIM >= iHeight) { } else { iYScans++; } } } } } if (pProgress) { delete pProgress; pProgress = 0; } } } else { ErrorBox("DStamp_ReadImage(): Supplied image must be 24 or 32 bit"); } return psMessage; } ////////////////// eof ////////////////