1124 lines
31 KiB
C++
1124 lines
31 KiB
C++
//
|
|
// Emscripten/SDL2/OpenGLES2 sample that demonstrates simple geometry and shaders, mouse and touch input, and window resizing
|
|
//
|
|
// Setup:
|
|
// Install emscripten: http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
|
|
//
|
|
// Build:
|
|
// emcc -std=c++11 hello_triangle.cpp events.cpp camera.cpp -s USE_SDL=2 -s FULL_ES2=1 -s WASM=0 -o hello_triangle.html
|
|
//
|
|
// Run:
|
|
// emrun hello_triangle.html
|
|
//
|
|
// Result:
|
|
// A colorful triangle. Left mouse pans, mouse wheel zooms in/out. Window is resizable.
|
|
//
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten.h>
|
|
#endif
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_opengles2.h>
|
|
//#include <SDL2/SDL_opengles2_gl2ext.h>
|
|
//#include <GLES3/gl3.h>
|
|
|
|
#include <dcmtk/ofstd/ofstring.h>
|
|
#include "dcmtk/config/osconfig.h"
|
|
#include "dcmtk/dcmdata/dcfilefo.h"
|
|
#include "dcmtk/dcmdata/dcdeftag.h"
|
|
#include "dcmtk/dcmdata/dcuid.h"
|
|
#include "dcmtk/dcmdata/dcmetinf.h"
|
|
#include "dcmtk/dcmdata/dcdict.h"
|
|
#include "dcmtk/dcmdata/dcdicent.h"
|
|
#include "dcmtk/dcmdata/dcxfer.h"
|
|
|
|
#include "dcmtk/dcmjpeg/djdecode.h" /* for JPEG decoders */
|
|
#include "dcmtk/dcmjpeg/djencode.h" /* for JPEG encoders */
|
|
#include "dcmtk/dcmjpls/djdecode.h" /* for JPEG-LS decoders */
|
|
#include "dcmtk/dcmjpls/djencode.h" /* for JPEG-LS encoders */
|
|
#include "dcmtk/dcmdata/dcrledrg.h" /* for RLE decoder */
|
|
#include "dcmtk/dcmdata/dcrleerg.h" /* for RLE encoder */
|
|
#include "dcmtk/dcmjpeg/dipijpeg.h" /* for dcmimage JPEG plugin */
|
|
#include "dcmtk/dcmjpeg/djrplol.h"
|
|
|
|
|
|
#include "dcmtk/dcmimage/diregist.h"
|
|
#include "dcmtk/dcmdata/dcpixseq.h"
|
|
#include "dcmtk/dcmjpeg/djcparam.h"
|
|
#include "dcmtk/dcmjpeg/djeijg8.h"
|
|
#include "dcmtk/dcmjpeg/djcodece.h"
|
|
#include "dcmtk/dcmdata/dcchrstr.h"
|
|
#include "dcmtk/ofstd/ofchrenc.h"
|
|
|
|
#include "dcmtk/dcmdata/dcvrui.h"
|
|
#include "dcmtk/dcmdata/dcvrsh.h"
|
|
#include "dcmtk/dcmdata/dcistrmb.h"
|
|
#include "dcmtk/oflog/loglevel.h"
|
|
|
|
|
|
#include "events.h"
|
|
|
|
typedef void (*callback_UpdateDcmImage) ();
|
|
callback_UpdateDcmImage callback_UpdateDcmImageComplete = NULL;
|
|
|
|
|
|
|
|
DcmDataset* g_pDcmDataset = NULL;
|
|
DcmElement* g_pixelDataElement = NULL;
|
|
bool g_bChange = false;
|
|
|
|
EventHandler* g_pEventHandler = NULL;
|
|
|
|
GLuint textureObj = 0;
|
|
|
|
GLuint g_shaderProgramGray16 = 0;
|
|
GLuint g_shaderProgramRGB = 0;
|
|
|
|
// Vertex shader
|
|
GLint g_shaderPanGray16, g_shaderZoomGray16, g_shaderAspectGray16, g_shaderWindowCenterGray16, g_shaderWindowWidthGray16;
|
|
GLint g_shaderPanRGB, g_shaderZoomRGB, g_shaderAspectRGB, g_shaderWindowCenterRGB, g_shaderWindowWidthRGB;
|
|
|
|
int g_nWindowWidth = 0;
|
|
int g_nWindowCenter = 0;
|
|
|
|
int g_nPrevWindowWidth = 0;
|
|
int g_nPrevWindowCenter = 0;
|
|
|
|
int g_nDefaultWindowWidth = 0;
|
|
int g_nDefaultWindowCenter = 0;
|
|
|
|
int g_nColorType = 0;
|
|
|
|
Uint32 g_nCurrentFrame = 0;
|
|
int g_nTotalFrames = 0;
|
|
|
|
int g_nFrameTextureWidth = 0;
|
|
int g_nFrameTextureHeight = 0;
|
|
|
|
int g_nSamplesPerPixel = 0;
|
|
int g_nBitsAllocated = 0;
|
|
|
|
int g_nFrameSizeUncompressed = 0;
|
|
|
|
int g_nWindowSizeWidth = 0;
|
|
int g_nWindowSizeHeight = 0;
|
|
|
|
float g_fFrameTime = 0;
|
|
float g_fFrameDelay = 0;
|
|
|
|
|
|
|
|
char* g_pBuf = NULL;
|
|
|
|
const GLchar* vertexSourceGray16 =
|
|
"uniform vec2 pan; \n"
|
|
"uniform float zoom; \n"
|
|
"uniform float aspect; \n"
|
|
"attribute vec4 position; \n"
|
|
"attribute vec2 a_texCoord; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_Position = vec4(position.xyz, 1.0); \n"
|
|
" gl_Position.xy += pan; \n"
|
|
" gl_Position.xy *= zoom; \n"
|
|
" texCoord = a_texCoord; \n"
|
|
" gl_Position.y *= aspect; \n"
|
|
"} \n";
|
|
|
|
|
|
// Fragment/pixel shader
|
|
const GLchar* fragmentSourceGray16 =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" vec4 colorOut = texture2D(texSampler, texCoord); \n"
|
|
" float fMin = fWindowCenter - fWindowWidth/2.0;\n"
|
|
" float fMax = fWindowCenter + fWindowWidth/2.0;\n"
|
|
" float fData = ( ((colorOut.a + colorOut.r*256.0)*256.0) - fMin)/(fMax-fMin); \n"
|
|
" fData = ( ((colorOut.a + colorOut.a*256.0)*256.0) - fMin)/(fMax-fMin); \n"
|
|
" fData = colorOut.r;\n"
|
|
" fData = (((colorOut.a * 256.0 + colorOut.r)*256.0) - fMin) / (fMax-fMin);"
|
|
" float fTmpData = fMin;\n"
|
|
" gl_FragColor = vec4(fData, fData, fData, 1.0); \n"
|
|
|
|
"} \n";
|
|
|
|
|
|
|
|
const GLchar* vertexSourceRGB =
|
|
"uniform vec2 panRGB; \n"
|
|
"uniform float zoomRGB; \n"
|
|
"uniform float aspectRGB; \n"
|
|
"attribute vec4 position; \n"
|
|
"attribute vec2 a_texCoord; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_Position = vec4(position.xyz, 1.0); \n"
|
|
" gl_Position.xy += panRGB; \n"
|
|
" gl_Position.xy *= zoomRGB; \n"
|
|
" texCoord = a_texCoord; \n"
|
|
" gl_Position.y *= aspectRGB; \n"
|
|
"} \n";
|
|
|
|
|
|
// Fragment/pixel shader
|
|
const GLchar* fragmentSourceRGB =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenterRGB; \n"
|
|
"uniform float fWindowWidthRGB; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" vec4 colorOut = texture2D(texSampler, texCoord); \n"
|
|
" float fMin = fWindowCenterRGB - fWindowWidthRGB/2.0;\n"
|
|
" float fMax = fWindowCenterRGB + fWindowWidthRGB/2.0;\n"
|
|
" float fTestValue = (fMax - fMin);\n"
|
|
" float fR = (colorOut.r*256.0 - fMin) / (fMax-fMin);\n"
|
|
" float fG = (colorOut.g*256.0 - fMin) / (fMax-fMin);\n"
|
|
" float fB = (colorOut.b*256.0 - fMin) / (fMax-fMin);\n"
|
|
" gl_FragColor = vec4(fR, fG, fB, 1.0); \n"
|
|
//" gl_FragColor = vec4(colorOut.r/fTestValue, 1.0, 1.0, 1.0); \n"
|
|
|
|
"} \n";
|
|
|
|
void updateShader(EventHandler& eventHandler)
|
|
{
|
|
Camera& camera = eventHandler.camera();
|
|
|
|
printf("updateShader: g_nColorType=%d\n", g_nColorType);
|
|
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
glUseProgram(g_shaderProgramGray16);
|
|
|
|
glUniform2fv(g_shaderPanGray16, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomGray16, camera.zoom());
|
|
glUniform1f(g_shaderAspectGray16, camera.aspect());
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
glUseProgram(g_shaderProgramRGB);
|
|
|
|
glUniform2fv(g_shaderPanRGB, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomRGB, camera.zoom());
|
|
glUniform1f(g_shaderAspectRGB, camera.aspect());
|
|
}
|
|
|
|
}
|
|
|
|
void onErrorData(void* arg)
|
|
{
|
|
printf("onErrorData %d\n", (int)arg);
|
|
}
|
|
|
|
void updateTextureGray16(int nWidth, int nHeight, void* pData)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, nWidth, nHeight, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pData);
|
|
}
|
|
|
|
void updateTextureRGB(int nWidth, int nHeight, void* pData)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, nWidth, nHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);
|
|
}
|
|
|
|
void updateWindowWidthLevelGray16(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterGray16, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthGray16, (float)nWidth);
|
|
}
|
|
|
|
void updateWindowWidthLevelRGB(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterRGB, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthRGB, (float)nWidth);
|
|
}
|
|
|
|
|
|
|
|
|
|
void initShaderGray16()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceGray16, NULL);
|
|
glCompileShader(vertexShader);
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceGray16, NULL);
|
|
glCompileShader(fragmentShader);
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramGray16 = glCreateProgram();
|
|
glAttachShader(g_shaderProgramGray16, vertexShader);
|
|
glAttachShader(g_shaderProgramGray16, fragmentShader);
|
|
glLinkProgram(g_shaderProgramGray16);
|
|
//glUseProgram(g_shaderProgramGray16);
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanGray16 = glGetUniformLocation(g_shaderProgramGray16, "pan");
|
|
g_shaderZoomGray16 = glGetUniformLocation(g_shaderProgramGray16, "zoom");
|
|
g_shaderAspectGray16 = glGetUniformLocation(g_shaderProgramGray16, "aspect");
|
|
g_shaderWindowCenterGray16 = glGetUniformLocation(g_shaderProgramGray16, "fWindowCenter");
|
|
g_shaderWindowWidthGray16 = glGetUniformLocation(g_shaderProgramGray16, "fWindowWidth");
|
|
|
|
glUniform1f(g_shaderWindowCenterGray16, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthGray16, 255.0f);
|
|
//printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterGray16:%d, g_shaderWindowWidthGray16:%d\n", g_shaderPanGray16, g_shaderZoomGray16, g_shaderAspectGray16, g_shaderWindowCenterGray16, g_shaderWindowWidthGray16);
|
|
|
|
|
|
}
|
|
|
|
|
|
void initShaderRGB()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceRGB, NULL);
|
|
glCompileShader(vertexShader);
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceRGB, NULL);
|
|
glCompileShader(fragmentShader);
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramRGB = glCreateProgram();
|
|
//printf("g_shaderProgramRGB: %d\n", g_shaderProgramRGB);
|
|
glAttachShader(g_shaderProgramRGB, vertexShader);
|
|
glAttachShader(g_shaderProgramRGB, fragmentShader);
|
|
glLinkProgram(g_shaderProgramRGB);
|
|
//glUseProgram(g_shaderProgramRGB);
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanRGB = glGetUniformLocation(g_shaderProgramRGB, "panRGB");
|
|
g_shaderZoomRGB = glGetUniformLocation(g_shaderProgramRGB, "zoomRGB");
|
|
g_shaderAspectRGB = glGetUniformLocation(g_shaderProgramRGB, "aspectRGB");
|
|
g_shaderWindowCenterRGB = glGetUniformLocation(g_shaderProgramRGB, "fWindowCenterRGB");
|
|
g_shaderWindowWidthRGB = glGetUniformLocation(g_shaderProgramRGB, "fWindowWidthRGB");
|
|
|
|
glUniform1f(g_shaderWindowCenterRGB, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthRGB, 255.0f);
|
|
//printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterRGB:%d, g_shaderWindowWidthRGB:%d\n", g_shaderPanRGB, g_shaderZoomRGB, g_shaderAspectRGB, g_shaderWindowCenterRGB, g_shaderWindowWidthRGB);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLuint initShader(EventHandler& eventHandler)
|
|
{
|
|
initShaderGray16();
|
|
initShaderRGB();
|
|
updateShader(eventHandler);
|
|
|
|
g_pEventHandler = &eventHandler;
|
|
|
|
return g_shaderProgramGray16;
|
|
}
|
|
|
|
GLuint g_vbo = 0;
|
|
|
|
void initGeometry(GLuint shaderProgram)
|
|
{
|
|
// Create vertex buffer object and copy vertex data into it
|
|
|
|
//printf("g_vbo: %d\n", g_vbo);
|
|
|
|
g_nWindowSizeWidth = (int)g_pEventHandler->camera().windowSize().width;
|
|
g_nWindowSizeHeight = (int)g_pEventHandler->camera().windowSize().height;
|
|
|
|
|
|
float fWindowWidth = (float)g_pEventHandler->camera().windowSize().width;
|
|
float fWindowHeight = (float)g_pEventHandler->camera().windowSize().height;
|
|
|
|
float fWindowRatio = fWindowWidth / fWindowHeight;
|
|
|
|
float fFrameTextureRatio = (float)g_nFrameTextureWidth / (float)g_nFrameTextureHeight;
|
|
|
|
float fRatioX = 1.0f;
|
|
float fRatioY = 1.0f;
|
|
|
|
|
|
float fRefWidthRatio = (float)g_nFrameTextureWidth / fWindowWidth;
|
|
float fRefHeightRatio = (float)g_nFrameTextureHeight / fWindowHeight;
|
|
|
|
|
|
if(fRefWidthRatio>fRefHeightRatio)
|
|
{
|
|
fRatioX = 1.0f;
|
|
fRatioY = (fWindowWidth*((float)g_nFrameTextureHeight/(float)g_nFrameTextureWidth)) / fWindowHeight;
|
|
}
|
|
else
|
|
{
|
|
fRatioY = 1.0f;
|
|
fRatioX = (fWindowHeight*((float)g_nFrameTextureWidth/(float)g_nFrameTextureHeight)) / fWindowWidth;
|
|
}
|
|
|
|
/*
|
|
if(fRatioX>1.0f || fRatioY>1.0f)
|
|
{
|
|
float fMax = fRatioX;
|
|
if(fRatioX<fRatioY)
|
|
{
|
|
fMax = fRatioY;
|
|
}
|
|
|
|
fRatioX = fRatioX / fMax;
|
|
fRatioY = fRatioY / fMax;
|
|
}
|
|
|
|
fRatioX = 1.0f;
|
|
fRatioY = 1.0f;
|
|
*/
|
|
printf("fWindowRatio:%f, fFrameTextureRatio:%f, fRatioX:%f, fRatioY:%f, (g_nWindowWidth:%d, g_nWindowHeight:%d)\n",
|
|
fWindowRatio, fFrameTextureRatio, fRatioX, fRatioY, g_nWindowSizeWidth, g_nWindowSizeHeight);
|
|
|
|
if(g_vbo==0)
|
|
{
|
|
glGenBuffers(1, &g_vbo);
|
|
|
|
}
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
|
|
GLfloat vertices[] =
|
|
{
|
|
-1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 0.0f, 1.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
|
|
1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 1.0f, 0.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
};
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
// Specify the layout of the shader vertex data (positions only, 3 floats)
|
|
GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
|
|
glEnableVertexAttribArray(posAttrib);
|
|
// glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)0);
|
|
// glEnableVertexAttribArray(0);
|
|
|
|
GLint texAttrib = glGetAttribLocation(shaderProgram, "a_texCoord");
|
|
glEnableVertexAttribArray(texAttrib);
|
|
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
|
|
//glEnableVertexAttribArray(2);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
//printf("initGeometry(%d, %d)\n", posAttrib, texAttrib);
|
|
|
|
|
|
|
|
}
|
|
|
|
void PrintError()
|
|
{
|
|
int nError = 0;
|
|
nError = glGetError();
|
|
printf("Error Code: %d\n", nError);
|
|
}
|
|
|
|
|
|
void onLoadedData(void* arg, void* buffer, int nSize)
|
|
{
|
|
//printf("onLoadedData %d, %d\n", (int)arg, nSize);
|
|
|
|
OFCondition error;
|
|
E_TransferSyntax opt_ixfer = EXS_Unknown;
|
|
E_FileReadMode opt_readMode = ERM_autoDetect;
|
|
Uint8* pData = (Uint8*)buffer;
|
|
|
|
g_nCurrentFrame = 0;
|
|
|
|
int nFindIndex = 0;
|
|
bool bFind = false;
|
|
int i=0;
|
|
Uint8* pDataTmp = (Uint8*)buffer;
|
|
for(i=0 ; i<nSize-4 && bFind==false ; i++)
|
|
{
|
|
if(pDataTmp[i]=='D' && pDataTmp[i+1]=='I' && pDataTmp[i+2]=='C' && pDataTmp[i+3]=='M')
|
|
{
|
|
nFindIndex = i;
|
|
bFind = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
memset(pData, 0x00, nFindIndex-1);
|
|
|
|
g_fFrameTime = 0;
|
|
g_fFrameDelay = 0;
|
|
|
|
// printf("nFindIndex = 0x%x", nFindIndex);
|
|
|
|
|
|
DcmInputBufferStream dcmBuffer;
|
|
dcmBuffer.setBuffer(pData, nSize);
|
|
dcmBuffer.setEos();
|
|
|
|
DcmInputBufferStream dcmBuffer2;
|
|
dcmBuffer2.setBuffer(pData, nSize);
|
|
dcmBuffer2.setEos();
|
|
|
|
|
|
// printf("%02x %02x %02x %02x\n", pData[0], pData[1], pData[2], pData[3]);
|
|
|
|
if(g_pDcmDataset!=NULL)
|
|
{
|
|
delete g_pDcmDataset;
|
|
}
|
|
g_pDcmDataset = new DcmDataset;
|
|
|
|
|
|
|
|
OFString strTransferSyntaxUID;
|
|
|
|
g_pDcmDataset->transferInit();
|
|
{
|
|
error = g_pDcmDataset->read(dcmBuffer2, EXS_LittleEndianExplicit);
|
|
g_pDcmDataset->findAndGetOFString(DCM_TransferSyntaxUID, strTransferSyntaxUID);
|
|
g_pDcmDataset->transferEnd();
|
|
dcmBuffer2.releaseBuffer();
|
|
|
|
delete g_pDcmDataset;
|
|
g_pDcmDataset = new DcmDataset;
|
|
}
|
|
if (strTransferSyntaxUID.size() > 0)
|
|
{
|
|
|
|
}
|
|
|
|
DcmXfer dcmXfer(strTransferSyntaxUID.c_str());
|
|
|
|
|
|
|
|
g_pDcmDataset->transferInit();
|
|
|
|
//error = g_pDcmDataset->read(dcmBuffer, EXS_JPEGProcess14SV1);
|
|
error = g_pDcmDataset->read(dcmBuffer, dcmXfer.getXfer());
|
|
if(error.bad())
|
|
{
|
|
printf("Dataset State: %u(%s)\n", g_pDcmDataset->transferState(), error.text());
|
|
}
|
|
|
|
g_pDcmDataset->transferEnd();
|
|
|
|
dcmBuffer.releaseBuffer();
|
|
|
|
|
|
g_nTotalFrames = 1;
|
|
g_nFrameTextureWidth = 0;
|
|
g_nFrameTextureHeight = 0;
|
|
g_nSamplesPerPixel = 1;
|
|
g_nBitsAllocated = 8;
|
|
|
|
|
|
Uint16 val = 0;
|
|
g_pDcmDataset->findAndGetUint16(DCM_Rows, val);
|
|
g_nFrameTextureHeight = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_Columns, val);
|
|
g_nFrameTextureWidth = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_SamplesPerPixel, val);
|
|
g_nSamplesPerPixel = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_BitsAllocated, val);
|
|
g_nBitsAllocated = val;
|
|
|
|
int nMaxValue = pow(2, g_nBitsAllocated) - 1;
|
|
int nMinValue = 0;
|
|
|
|
int nDefaultWindowWidth = nMaxValue - nMinValue;
|
|
int nDefaultWindowCenter = (nMaxValue + nMinValue)/2;
|
|
|
|
Sint32 nFrames = 0;
|
|
OFString strTmp;
|
|
OFString strFrames;
|
|
// g_pDcmDataset->findAndGetSint32(DCM_NumberOfFrames, nFrames);
|
|
g_pDcmDataset->findAndGetOFString(DCM_NumberOfFrames, strFrames);
|
|
|
|
nFrames = atoi(strFrames.c_str());
|
|
if(nFrames<=0)
|
|
{
|
|
nFrames = 1;
|
|
}
|
|
else
|
|
{
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_FrameTime, strTmp);
|
|
printf("DCM_Frametime: %s\n", strTmp.c_str());
|
|
|
|
g_fFrameTime = atof(strTmp.c_str());
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_FrameDelay, strTmp);
|
|
printf("DCM_FrameDelay: %s\n", strTmp.c_str());
|
|
|
|
g_fFrameDelay = atof(strTmp.c_str());
|
|
}
|
|
g_nTotalFrames = nFrames;
|
|
|
|
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_WindowCenter, strTmp);
|
|
g_nDefaultWindowCenter = atoi(strTmp.c_str());
|
|
if(g_nDefaultWindowCenter==0)
|
|
{
|
|
g_nDefaultWindowCenter = nDefaultWindowCenter;
|
|
}
|
|
if(g_nWindowCenter<=0 && g_nDefaultWindowCenter)
|
|
{
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
}
|
|
|
|
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_WindowWidth, strTmp);
|
|
g_nDefaultWindowWidth = atoi(strTmp.c_str());
|
|
if(g_nDefaultWindowWidth==0)
|
|
{
|
|
g_nDefaultWindowWidth = nDefaultWindowWidth;
|
|
}
|
|
if(g_nWindowWidth<=0 && g_nDefaultWindowWidth>0)
|
|
{
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
}
|
|
|
|
|
|
/*
|
|
Float64 fValue = 0.0f;
|
|
fValue = 0;
|
|
g_nDefaultWindowCenter = 0;
|
|
g_pDcmDataset->findAndGetFloat64(DCM_WindowCenter, fValue);
|
|
g_nDefaultWindowCenter = (int)val;
|
|
if(g_nWindowCenter<=0 && g_nDefaultWindowCenter)
|
|
{
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
}
|
|
|
|
fValue = 0;
|
|
g_pDcmDataset->findAndGetFloat64(DCM_WindowWidth, fValue);
|
|
g_nDefaultWindowWidth = (int)val;
|
|
if(g_nWindowWidth<=0 && g_nDefaultWindowWidth>0)
|
|
{
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
}
|
|
*/
|
|
|
|
printf("File WindowWidth: %d, WindowCenter: %d\n", g_nDefaultWindowWidth, g_nDefaultWindowCenter);
|
|
|
|
|
|
|
|
g_nPrevWindowCenter = g_nWindowCenter;
|
|
g_nPrevWindowWidth = g_nWindowWidth;
|
|
|
|
|
|
|
|
if(g_nSamplesPerPixel==3 && g_nBitsAllocated==8)
|
|
{
|
|
g_nColorType = 1;
|
|
}
|
|
else
|
|
{
|
|
g_nColorType = 0;
|
|
}
|
|
|
|
|
|
printf("ColorType:%d, Width:%d, Height:%d, TotalFrames:(%s)(%d) SamplesPerPixel:%d, BitsAllocated:%d\n", g_nColorType, g_nFrameTextureWidth, g_nFrameTextureHeight, strFrames.c_str(), g_nTotalFrames, g_nSamplesPerPixel, g_nBitsAllocated);
|
|
|
|
if(g_nFrameTextureWidth==0)
|
|
{
|
|
DcmElement* pElement = NULL;
|
|
g_pDcmDataset->findAndGetElement(DCM_Columns, pElement);
|
|
printf("pElement: %x\n", pElement);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_pixelDataElement = NULL;
|
|
g_pDcmDataset->findAndGetElement(DCM_PixelData, g_pixelDataElement);
|
|
|
|
printf("OnLoadedData: g_pixelDataElement: %x\n", g_pixelDataElement);
|
|
|
|
const Uint8* pImageData = NULL;
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
|
|
if(pImageData!=NULL)
|
|
{
|
|
error = g_pDcmDataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
|
|
if(error.bad())
|
|
{
|
|
printf("chooseRepresentation: %s\n", error.text());
|
|
}
|
|
else
|
|
{
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("Find Pixel Data!!!\n");
|
|
if(g_pixelDataElement->isEmpty()==true)
|
|
{
|
|
printf("PixelData is Empty!!\n");
|
|
}
|
|
else
|
|
{
|
|
printf("ok0\n");
|
|
Uint32 nFrameSizeUncompressed = 0;
|
|
g_pixelDataElement->getUncompressedFrameSize(g_pDcmDataset, nFrameSizeUncompressed);
|
|
g_nFrameSizeUncompressed = nFrameSizeUncompressed;
|
|
|
|
printf("ok1: %d\n", g_nFrameSizeUncompressed);
|
|
|
|
if(g_pBuf!=NULL)
|
|
{
|
|
delete[] g_pBuf;
|
|
}
|
|
|
|
g_pBuf = new char[g_nFrameSizeUncompressed];
|
|
|
|
OFCondition ofTest;
|
|
OFString decompressedColorModel;
|
|
|
|
Uint32 startFragment = 0;
|
|
|
|
|
|
ofTest = g_pixelDataElement->getUncompressedFrame(g_pDcmDataset, 1, startFragment, g_pBuf, g_nFrameSizeUncompressed, decompressedColorModel);
|
|
|
|
//printf("updateTextureRGB: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, startFragment, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
|
|
updateTextureRGB(g_nFrameTextureWidth, g_nFrameTextureHeight, g_pBuf);
|
|
|
|
|
|
//printf("sizeF: %d\n", sizeF);
|
|
}
|
|
}
|
|
|
|
|
|
if(callback_UpdateDcmImageComplete!=NULL)
|
|
{
|
|
printf("callback_UpdateDcmImageComplete\n");
|
|
callback_UpdateDcmImageComplete();
|
|
}
|
|
|
|
g_bChange = true;
|
|
//g_bChange = false;
|
|
|
|
//printf("load OK!\n");
|
|
}
|
|
|
|
|
|
|
|
|
|
void initTextureGray16()
|
|
{
|
|
|
|
const Uint16* pImageData = NULL;
|
|
g_pDcmDataset->findAndGetUint16Array(DCM_PixelData, pImageData);
|
|
|
|
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
{
|
|
glGenTextures(1, &textureObj);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, textureObj);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
|
|
PrintError();
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pImageData);
|
|
PrintError();
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramGray16, "texSampler");
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glUniform1i(nTextureID, 0);
|
|
}
|
|
}
|
|
|
|
|
|
void initTextureRGB()
|
|
{
|
|
const Uint8* pImageData = NULL;
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
GLint format = GL_RGB;
|
|
|
|
if(textureObj>=0)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
glDeleteTextures(1, &textureObj);
|
|
}
|
|
|
|
glGenTextures(1, &textureObj);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, textureObj);
|
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
|
|
PrintError();
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, pImageData);
|
|
|
|
PrintError();
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramRGB, "texSampler");
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glUniform1i(nTextureID, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void redraw(EventHandler& eventHandler)
|
|
{
|
|
|
|
int nWindowSizeWidth = (int)g_pEventHandler->camera().windowSize().width;
|
|
int nWindowSizeHeight = (int)g_pEventHandler->camera().windowSize().height;
|
|
|
|
bool bSizeChange = false;
|
|
|
|
if(nWindowSizeWidth != g_nWindowSizeWidth ||
|
|
nWindowSizeHeight != g_nWindowSizeHeight)
|
|
{
|
|
bSizeChange = true;
|
|
|
|
}
|
|
|
|
if(g_bChange==true)
|
|
{
|
|
if(g_nColorType==0)
|
|
{
|
|
printf("redraw: initTextureGray16\n");
|
|
initGeometry(g_shaderProgramGray16);
|
|
glUseProgram(g_shaderProgramGray16);
|
|
initTextureGray16();
|
|
updateShader(*g_pEventHandler);
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
printf("redraw: initTextureRGB\n");
|
|
initGeometry(g_shaderProgramRGB);
|
|
glUseProgram(g_shaderProgramRGB);
|
|
initTextureRGB();
|
|
updateShader(*g_pEventHandler);
|
|
}
|
|
g_bChange = false;
|
|
}
|
|
|
|
if(bSizeChange==true)
|
|
{
|
|
if(g_nColorType==0)
|
|
{
|
|
printf("redraw: SizeChange initTextureGray16\n");
|
|
initGeometry(g_shaderProgramGray16);
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
printf("redraw: SizeChange initTextureRGB\n");
|
|
initGeometry(g_shaderProgramRGB);
|
|
}
|
|
|
|
g_nWindowSizeWidth = (int)g_pEventHandler->camera().windowSize().width;
|
|
g_nWindowSizeHeight = (int)g_pEventHandler->camera().windowSize().height;
|
|
}
|
|
|
|
if(g_nTotalFrames>1)
|
|
{
|
|
OFCondition ofTest;
|
|
OFString decompressedColorModel;
|
|
|
|
Uint32 nDisplayFrameIndex = g_nCurrentFrame;
|
|
|
|
ofTest = g_pixelDataElement->getUncompressedFrame(g_pDcmDataset, 1, nDisplayFrameIndex, g_pBuf, g_nFrameSizeUncompressed, decompressedColorModel);
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
|
|
updateTextureGray16(g_nFrameTextureWidth, g_nFrameTextureHeight, g_pBuf);
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
|
|
//printf("updateTextureRGB: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
updateTextureRGB(g_nFrameTextureWidth, g_nFrameTextureHeight, g_pBuf);
|
|
}
|
|
|
|
if(g_nCurrentFrame>=g_nTotalFrames)
|
|
{
|
|
g_nCurrentFrame = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
const Uint8* pImageData = NULL;
|
|
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
printf("updateTextureGray16: %d\n", g_nCurrentFrame);
|
|
updateTextureGray16(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)pImageData);
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
printf("updateTextureRGB: %d, %d, %x\n", g_nFrameTextureWidth, g_nFrameTextureHeight, pImageData);
|
|
//printf("updateTextureRGB: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
updateTextureRGB(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)pImageData);
|
|
}
|
|
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
updateWindowWidthLevelGray16(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
updateWindowWidthLevelRGB(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
|
|
}
|
|
// Clear screen
|
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
//glUseProgram(g_shaderProgramGray16);
|
|
|
|
// glActiveTexture(GL_TEXTURE0);
|
|
// glBindTexture(GL_TEXTURE_2D, textureObj);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
|
|
|
|
// Draw the vertex buffer
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
//glDrawArrays(GL_QUADS, 0, 4);
|
|
|
|
// Swap front/back framebuffers
|
|
eventHandler.swapWindow();
|
|
|
|
//printf("redraw\n");
|
|
}
|
|
|
|
void mainLoop(void* mainLoopArg)
|
|
{
|
|
EventHandler& eventHandler = *((EventHandler*)mainLoopArg);
|
|
eventHandler.processEvents();
|
|
|
|
int nType = eventHandler.GetEventType();
|
|
if(nType==1 || nType==2)
|
|
{
|
|
float fDeltaX = eventHandler.GetDeltaX();
|
|
float fDeltaY = eventHandler.GetDeltaY();
|
|
|
|
|
|
g_nPrevWindowCenter = g_nWindowCenter+fDeltaY;
|
|
g_nPrevWindowWidth = g_nWindowWidth + fDeltaX;
|
|
|
|
|
|
|
|
|
|
//printf("DeltaX: %f, DeltaY:%f, (%d, %d)\n", fDeltaX, fDeltaY, g_nPrevWindowCenter, g_nPrevWindowWidth);
|
|
|
|
// glUniform1f(g_shaderWindowCenterGray16, (float)g_nPrevWindowCenter);
|
|
// glUniform1f(g_shaderWindowWidthGray16, (float)g_nPrevWindowWidth);
|
|
//
|
|
|
|
}
|
|
else if(nType==3 || nType==4)
|
|
{
|
|
g_nWindowCenter = g_nPrevWindowCenter;
|
|
g_nWindowWidth = g_nPrevWindowWidth;
|
|
|
|
}
|
|
|
|
// glUniform1f(g_shaderWindowCenterGray16, (float)g_nPrevWindowCenter);
|
|
// glUniform1f(g_shaderWindowWidthGray16, (float)g_nPrevWindowWidth);
|
|
|
|
// Update shader if camera changed
|
|
if (eventHandler.camera().updated())
|
|
updateShader(eventHandler);
|
|
|
|
redraw(eventHandler);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
#define EXTERN extern "C"
|
|
#else
|
|
#define EXTERN
|
|
#endif
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int UpdateDcmImage(std::string strFile)
|
|
{
|
|
printf("UpdateDcmImage: %s\n", strFile.c_str());
|
|
|
|
emscripten_async_wget_data(strFile.c_str(), (void*)135, onLoadedData, onErrorData);
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetWindowWidthLevel(int nWindowCenter, int nWindowWidth)
|
|
{
|
|
printf("New nWindowCenter:%d, nWindowWidth:%d\n", nWindowCenter, nWindowWidth);
|
|
|
|
g_nWindowCenter = nWindowCenter;
|
|
g_nWindowWidth = nWindowWidth;
|
|
|
|
g_nPrevWindowCenter = g_nWindowCenter;
|
|
g_nPrevWindowWidth = g_nWindowWidth;
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetDisplayFrameIndex(int nIndex)
|
|
{
|
|
g_nCurrentFrame = nIndex;
|
|
|
|
if(g_nCurrentFrame>=g_nTotalFrames)
|
|
{
|
|
g_nCurrentFrame = g_nTotalFrames-1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetResizeFrame(int nWidth, int nHeight)
|
|
{
|
|
printf("SetResizeFrame: (%d, %d)\n", nWidth, nHeight);
|
|
|
|
g_pEventHandler->camera().setWindowSize(nWidth, nHeight);
|
|
return 0;
|
|
}
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetWindowWidth()
|
|
{
|
|
return g_nWindowWidth;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetWindowCenter()
|
|
{
|
|
return g_nWindowCenter;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetTotalFrames()
|
|
{
|
|
return g_nTotalFrames;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE float GetFrameUpdateTimeDelay()
|
|
{
|
|
return (g_fFrameDelay + g_fFrameTime);
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE bool SetCallbackUpdateDcmImageComplete(callback_UpdateDcmImage callback_)
|
|
{
|
|
printf("SetCallbackUpdateDcmImage: %x\n", callback_);
|
|
|
|
callback_UpdateDcmImageComplete = callback_;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
OFLog::configure(OFLogger::OFF_LOG_LEVEL);
|
|
EventHandler eventHandler("dcm image viewer");
|
|
|
|
// Initialize shader and geometry
|
|
GLuint shaderProgram = initShader(eventHandler);
|
|
|
|
//initTexture();
|
|
|
|
// Start the main loop
|
|
void* mainLoopArg = &eventHandler;
|
|
|
|
|
|
DJDecoderRegistration::registerCodecs();
|
|
DJLSDecoderRegistration::registerCodecs();
|
|
DcmRLEDecoderRegistration::registerCodecs();
|
|
|
|
//emscripten_async_wget_data("http://49.171.226.18:1901/3.dcm", (void*)135, onLoadedData, onErrorData);
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
int fps = 0; // Use browser's requestAnimationFrame
|
|
emscripten_set_main_loop_arg(mainLoop, mainLoopArg, fps, true);
|
|
#else
|
|
while(true)
|
|
mainLoop(mainLoopArg);
|
|
#endif
|
|
|
|
glDeleteTextures(1, &textureObj);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
DJDecoderRegistration::cleanup();
|
|
DJLSDecoderRegistration::cleanup();
|
|
DcmRLEDecoderRegistration::cleanup();
|
|
|
|
if(g_pDcmDataset!=NULL)
|
|
{
|
|
delete g_pDcmDataset;
|
|
g_pDcmDataset = NULL;
|
|
}
|
|
|
|
printf("exit!!!!! c++ dcm_image_mod");
|
|
|
|
return 0;
|
|
}
|